Compare commits

...
101 Commits
Author SHA1 Message Date
Nick HollonandGitHub 35cea28707 release: alpha bump langgraph 1.2.0a6 (#7697) 2026-05-04 08:58:57 -04:00
Nick HollonandGitHub 3fff7bc928 feat(langgraph): forward kwargs through stream_events(version="v3") (#7696) 2026-05-04 08:52:52 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a0c4bdc3cb chore(deps): bump the minor-and-patch group across 1 directory with 6 updates (#7675)
Bumps the minor-and-patch group with 6 updates in the /libs/langgraph
directory:

| Package | From | To |
| --- | --- | --- |
| [xxhash](https://github.com/ifduyue/python-xxhash) | `3.6.0` | `3.7.0`
|
| [pydantic](https://github.com/pydantic/pydantic) | `2.12.5` | `2.13.3`
|
| [py-spy](https://github.com/benfred/py-spy) | `0.4.1` | `0.4.2` |
| [mypy](https://github.com/python/mypy) | `1.19.1` | `1.20.2` |
| [ruff](https://github.com/astral-sh/ruff) | `0.15.7` | `0.15.12` |
| [types-requests](https://github.com/python/typeshed) |
`2.32.4.20260324` | `2.33.0.20260408` |


Updates `xxhash` from 3.6.0 to 3.7.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/releases">xxhash's
releases</a>.</em></p>
<blockquote>
<h2>v3.7.0</h2>
<ul>
<li>Drop support for Python 3.7</li>
<li>Build armv7l manylinux/musllinux wheels</li>
<li>Build riscv64 manylinux/musllinux wheels</li>
<li>Build android and ios wheels</li>
</ul>
<hr />
<p>Full list of changes: <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.7.0">https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.7.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ifduyue/python-xxhash/blob/master/CHANGELOG.rst">xxhash's
changelog</a>.</em></p>
<blockquote>
<p>v3.7.0 2025-04-25</p>
<pre><code>
- Drop support for Python 3.7
- Build armv7l manylinux/musllinux wheels
- Build riscv64 manylinux/musllinux wheels
- Build android and ios wheels
</code></pre>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/404589e3d5735591755d2e7f32c87a15f8724c02"><code>404589e</code></a>
Prepare 3.7.0</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/d3cd66446da1bea6de24b4edebc31a84cee45aab"><code>d3cd664</code></a>
Bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/0d7d943288e3ad86988d57002ec5ebd3ce8e61da"><code>0d7d943</code></a>
Bump pypa/cibuildwheel from 3.4.0 to 3.4.1</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/c4b8775fe0fd60598e4244ea0c522134cc3c3cf5"><code>c4b8775</code></a>
Bump actions/download-artifact from 7 to 8</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/1ae3e5b60e4cf1f5f78278183dde90dba932962a"><code>1ae3e5b</code></a>
Bump actions/upload-artifact from 6 to 7</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/8faaa340c9a647dd78ce1c6515097b28f744352b"><code>8faaa34</code></a>
Bump pypa/cibuildwheel from 3.3.1 to 3.4.0</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/4ba9b0cc343c50f903ff67918032971e0d9842c3"><code>4ba9b0c</code></a>
Bump docker/setup-qemu-action from 3 to 4</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/43bb2030f1a7fd389cc876037e653674b850e9e7"><code>43bb203</code></a>
Bump actions/download-artifact from 6 to 7</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/a7a3bd22f2a536220bd08c5765e2fe06e9210ba8"><code>a7a3bd2</code></a>
Bump actions/upload-artifact from 5 to 6</li>
<li><a
href="https://github.com/ifduyue/python-xxhash/commit/c35bf0125bfe2a11794d7d5440159ed8b124e0e1"><code>c35bf01</code></a>
Bump pypa/cibuildwheel from 3.3.0 to 3.3.1</li>
<li>Additional commits viewable in <a
href="https://github.com/ifduyue/python-xxhash/compare/v3.6.0...v3.7.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `pydantic` from 2.12.5 to 2.13.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pydantic/pydantic/releases">pydantic's
releases</a>.</em></p>
<blockquote>
<h2>v2.13.3 2026-04-20</h2>
<h2>v2.13.3 (2026-04-20)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13096">#13096</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.2...v2.13.3">https://github.com/pydantic/pydantic/compare/v2.13.2...v2.13.3</a></p>
<h2>v2.13.2 2026-04-17</h2>
<h2>v2.13.2 (2026-04-17)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13084">#13084</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.1...v2.13.2">https://github.com/pydantic/pydantic/compare/v2.13.1...v2.13.2</a></p>
<h2>v2.13.1 2026-04-15</h2>
<h2>v2.13.1 (2026-04-15)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/davidhewitt"><code>@​davidhewitt</code></a> in
<a
href="https://redirect.github.com/pydantic/pydantic/pull/13079">#13079</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.0...v2.13.1">https://github.com/pydantic/pydantic/compare/v2.13.0...v2.13.1</a></p>
<h2>v2.13.0 2026-04-13</h2>
<h2>v2.13.0 (2026-04-13)</h2>
<p>The highlights of the v2.13 release are available in the <a
href="https://pydantic.dev/articles/pydantic-v2-13-release">blog
post</a>.
Several minor changes (considered non-breaking changes according to our
<a
href="https://pydantic.dev/docs/validation/2.13/get-started/version-policy/#pydantic-v2">versioning
policy</a>) are also included in this release. Make sure to look into
them before upgrading.</p>
<p>This release contains the updated <code>pydantic.v1</code> namespace,
matching version 1.10.26 which includes support for Python 3.14.</p>
<h3>What's Changed</h3>
<p>See the beta releases for all changes sinces 2.12.</p>
<h4>Packaging</h4>
<ul>
<li>Add zizmor for GitHub Actions workflow linting by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13039">#13039</a></li>
<li>Update jiter to v0.14.0 to fix a segmentation fault on musl Linux by
<a href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13064">#13064</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pydantic/pydantic/blob/main/HISTORY.md">pydantic's
changelog</a>.</em></p>
<blockquote>
<h2>v2.13.3 (2026-04-20)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.3">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13096">#13096</a></li>
</ul>
<h2>v2.13.2 (2026-04-17)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.2">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13084">#13084</a></li>
</ul>
<h2>v2.13.1 (2026-04-15)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.1">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/davidhewitt"><code>@​davidhewitt</code></a> in
<a
href="https://redirect.github.com/pydantic/pydantic/pull/13079">#13079</a></li>
</ul>
<h2>v2.13.0 (2026-04-13)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.0">GitHub
release</a></p>
<p>The highlights of the v2.13 release are available in the <a
href="https://pydantic.dev/articles/pydantic-v2-13-release">blog
post</a>.
Several minor changes (considered non-breaking changes according to our
<a
href="https://pydantic.dev/docs/validation/2.13/get-started/version-policy/#pydantic-v2">versioning
policy</a>)
are also included in this release. Make sure to look into them before
upgrading.</p>
<p>This release contains the updated <code>pydantic.v1</code> namespace,
matching version 1.10.26 which includes support for Python 3.14.</p>
<h3>What's Changed</h3>
<p>See the beta releases for all changes sinces 2.12.</p>
<h4>New Features</h4>
<ul>
<li>Allow default factories of private attributes to take validated
model data by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13013">#13013</a></li>
</ul>
<h4>Changes</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pydantic/pydantic/commit/9e9a11132c980c4ec84ff6d2df06d4c1a55ca8f3"><code>9e9a111</code></a>
Fix backported test</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/1ec8c6aa0cae1c78409c6f31af738a24e16c7124"><code>1ec8c6a</code></a>
Prepare release v2.13.3</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/fb4f2044436e9fa4ff9745479ce263016a99c4d6"><code>fb4f204</code></a>
Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/ca3ddd1149bd3b14273922a29da174804496a055"><code>ca3ddd1</code></a>
Prepare release v2.13.2</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/000e823a3ab7545b0c6064fde34effdcd02044ab"><code>000e823</code></a>
Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/d45d8be3ffb6dfc6d0c64da6980140c91ff841fb"><code>d45d8be</code></a>
Prepare release 2.13.1</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/54aca609ce58e73dfe1c681d93432fbe3a4621da"><code>54aca60</code></a>
Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/46bf4fa648af3a1fbf4603a37f210e9d9c618357"><code>46bf4fa</code></a>
Fix Pydantic release workflow (<a
href="https://redirect.github.com/pydantic/pydantic/issues/13067">#13067</a>)</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/1b359edab09c623464d23c6fd2503ae5ff276d43"><code>1b359ed</code></a>
Prepare release v2.13.0 (<a
href="https://redirect.github.com/pydantic/pydantic/issues/13065">#13065</a>)</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/b1bf19445d8ac144a7a0e82674d2d87eebab6c18"><code>b1bf194</code></a>
Fix model equality when using runtime <code>extra</code> configuration
(<a
href="https://redirect.github.com/pydantic/pydantic/issues/13062">#13062</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pydantic/pydantic/compare/v2.12.5...v2.13.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `py-spy` from 0.4.1 to 0.4.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/benfred/py-spy/releases">py-spy's
releases</a>.</em></p>
<blockquote>
<h2>v0.4.2</h2>
<h2>Changes</h2>
<h2>🚀 Features</h2>
<ul>
<li>Add Python 3.14 support <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/833">#833</a>)</li>
<li>Add support for native extensions on linux aarch64 <a
href="https://github.com/tehrengruber"><code>@​tehrengruber</code></a>
(<a
href="https://redirect.github.com/benfred/py-spy/issues/779">#779</a>)</li>
<li>Use Py_Version symbol for detecting the python version <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/835">#835</a>)</li>
<li>Show backtrace on errors when RUST_BACKTRACE=1 environment variable
is set <a href="https://github.com/benfred"><code>@​benfred</code></a>
(<a
href="https://redirect.github.com/benfred/py-spy/issues/841">#841</a>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li>Fix getting symbols from OSX universal binaries <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/843">#843</a>)</li>
<li>Fix intermittent OSX CI errors <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/842">#842</a>)</li>
<li>Fix corrupted <code>subprocess</code> output <a
href="https://github.com/ravwojdyla"><code>@​ravwojdyla</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/832">#832</a>)</li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>Minor code cleanups <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/844">#844</a>)</li>
<li>Set github actions workflow permissions <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/840">#840</a>)</li>
<li>Fix release drafter config <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/839">#839</a>)</li>
<li>Update dependencies <a
href="https://github.com/benfred"><code>@​benfred</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/838">#838</a>)</li>
<li>Bump the github-actions group across 1 directory with 6 updates @<a
href="https://github.com/apps/dependabot">dependabot[bot]</a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/836">#836</a>)</li>
<li>fix: make build and cargo clippy happy <a
href="https://github.com/yihong0618"><code>@​yihong0618</code></a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/795">#795</a>)</li>
<li>Bump the github-actions group with 2 updates @<a
href="https://github.com/apps/dependabot">dependabot[bot]</a> (<a
href="https://redirect.github.com/benfred/py-spy/issues/798">#798</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/benfred/py-spy/commit/d230d8208d652ba168256ffb66170d1c6cd8b650"><code>d230d82</code></a>
Bump version: 0.4.1 → 0.4.2</li>
<li><a
href="https://github.com/benfred/py-spy/commit/48af4e5d69bd016db193f9113c041b9baf105130"><code>48af4e5</code></a>
Minor code cleanups (<a
href="https://redirect.github.com/benfred/py-spy/issues/844">#844</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/13c8863569cdd220c3d1a611f7c87ee515a845a9"><code>13c8863</code></a>
Add support for native extensions on linux aarch64 (<a
href="https://redirect.github.com/benfred/py-spy/issues/779">#779</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/2982dab1df02958add15e7c9c60100c22580a2a8"><code>2982dab</code></a>
Use Py_Version symbol for detecting the python version (<a
href="https://redirect.github.com/benfred/py-spy/issues/835">#835</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/c478370afbd0ee6bb3706d00e7b33295bcaf4e22"><code>c478370</code></a>
Fix getting symbols from OSX universal binaries (<a
href="https://redirect.github.com/benfred/py-spy/issues/843">#843</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/344703d74c720c62e3990a635705df485c0cf078"><code>344703d</code></a>
Set github actions workflow permissions (<a
href="https://redirect.github.com/benfred/py-spy/issues/840">#840</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/f84084a403cd172d7593a75fd1128966c43f7bea"><code>f84084a</code></a>
Fix intermittent OSX CI errors (<a
href="https://redirect.github.com/benfred/py-spy/issues/842">#842</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/d7d22b616a6918a57f7ef11fbcb85afac41b2758"><code>d7d22b6</code></a>
Show backtrace on errors when RUST_BACKTRACE=1 environment variable is
set (#...</li>
<li><a
href="https://github.com/benfred/py-spy/commit/4580345efeb494f8d5efe251506f70d2af0c5f41"><code>4580345</code></a>
Fix corrupted <code>subprocess</code> output (<a
href="https://redirect.github.com/benfred/py-spy/issues/832">#832</a>)</li>
<li><a
href="https://github.com/benfred/py-spy/commit/39bbc405d41fc02deb4bc62ff39b4e010e4ab07f"><code>39bbc40</code></a>
Fix release drafter config (<a
href="https://redirect.github.com/benfred/py-spy/issues/839">#839</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/benfred/py-spy/compare/v0.4.1...v0.4.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `mypy` from 1.19.1 to 1.20.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's
changelog</a>.</em></p>
<blockquote>
<h3>Mypy 1.20.2</h3>
<ul>
<li>Use WAL with SQLite cache and fix close (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21154">21154</a>)</li>
<li>Adjust SQLite journal mode (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21217">21217</a>)</li>
<li>Correctly aggregate narrowing information on parent expressions
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21206">21206</a>)</li>
<li>Fix regression related to generic callables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21208">21208</a>)</li>
<li>Fix regression by avoiding widening types in some contexts
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21242">21242</a>)</li>
<li>Fix slicing in non-strict optional mode (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21282">21282</a>)</li>
<li>mypyc: Fix match statement semantics for &quot;or&quot; pattern
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21156">21156</a>)</li>
<li>mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR <a
href="https://redirect.github.com/python/mypy/pull/21275">21275</a>)</li>
<li>Initial support for Python 3.15.0a8 (Marc Mueller, PR <a
href="https://redirect.github.com/python/mypy/pull/21255">21255</a>)</li>
</ul>
<h3>Acknowledgements</h3>
<p>Thanks to all mypy contributors who contributed to this release:</p>
<ul>
<li>A5rocks</li>
<li>Aaron Wieczorek</li>
<li>Adam Turner</li>
<li>Ali Hamdan</li>
<li>asce</li>
<li>BobTheBuidler</li>
<li>Brent Westbrook</li>
<li>Brian Schubert</li>
<li>bzoracler</li>
<li>Chris Burroughs</li>
<li>Christoph Tyralla</li>
<li>Colin Watson</li>
<li>Donghoon Nam</li>
<li>E. M. Bray</li>
<li>Emma Smith</li>
<li>Ethan Sarp</li>
<li>George Ogden</li>
<li>getzze</li>
<li>grayjk</li>
<li>Gregor Riepl</li>
<li>Ivan Levkivskyi</li>
<li>James Hilliard</li>
<li>James Le Cuirot</li>
<li>Jeremy Nimmer</li>
<li>Joren Hammudoglu</li>
<li>Kai (Kazuya Ito)</li>
<li>kaushal trivedi</li>
<li>Kevin Kannammalil</li>
<li>Lukas Geiger</li>
<li>Łukasz Langa</li>
<li>Marc Mueller</li>
<li>Michael R. Crusoe</li>
<li>michaelm-openai</li>
<li>Neil Schemenauer</li>
<li>Piotr Sawicki</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/python/mypy/commit/145a062651b5f9996b75ef32b7040bd2e885ed82"><code>145a062</code></a>
Bump version to 1.20.2</li>
<li><a
href="https://github.com/python/mypy/commit/81cd49215c288eacb987de066f02daff2553b7c7"><code>81cd492</code></a>
Fix slicing with nonstrict optional (<a
href="https://redirect.github.com/python/mypy/issues/21282">#21282</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/908d3441eecbaa2a6193165317177db834d7ca1a"><code>908d344</code></a>
[mypyc] Set dunder attrs when adding module to sys.modules (<a
href="https://redirect.github.com/python/mypy/issues/21275">#21275</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/ba28610fac9d2b33be210ca8dcfe4bc47b7af424"><code>ba28610</code></a>
Initial support for Python 3.15.0a8 (<a
href="https://redirect.github.com/python/mypy/issues/21255">#21255</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/7b0e09f48dbd3717ed008a273cd17e8e960c2037"><code>7b0e09f</code></a>
Fix match statement semantics for &quot;or&quot; pattern (<a
href="https://redirect.github.com/python/mypy/issues/21156">#21156</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/92b74f226de62f7505f5ef5cb158e8ec9c58b8b7"><code>92b74f2</code></a>
Avoid widening types in conditional_types (<a
href="https://redirect.github.com/python/mypy/issues/21242">#21242</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/0dcbfaa40b0e360a16baea9cf851955375d91b54"><code>0dcbfaa</code></a>
Fix is_overlapping_types for generic callables (<a
href="https://redirect.github.com/python/mypy/issues/21208">#21208</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/210f518dede35292033ef0d387847406a0ccef8f"><code>210f518</code></a>
Correctly aggregate narrowing information on parent expressions (<a
href="https://redirect.github.com/python/mypy/issues/21206">#21206</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/c34530e53a10e385d8b0f1af4baa88a596b5ceaa"><code>c34530e</code></a>
Only set journal mode in coordinator (<a
href="https://redirect.github.com/python/mypy/issues/21217">#21217</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/79a3ec6d01b56a27c00e9b3320c2b1d4d73a77f9"><code>79a3ec6</code></a>
Use WAL with SQLite cache, fix close (<a
href="https://redirect.github.com/python/mypy/issues/21154">#21154</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/python/mypy/compare/v1.19.1...v1.20.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.7 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>0.15.11</h2>
<p>Released on 2026-04-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Ignore <code>RUF029</code> when function is
decorated with <code>asynccontextmanager</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24642">#24642</a>)</li>
<li>[<code>airflow</code>] Implement
<code>airflow-xcom-pull-in-template-string</code> (<code>AIR201</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/23583">#23583</a>)</li>
<li>[<code>flake8-bandit</code>] Fix <code>S103</code> false positives
and negatives in mask analysis (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24424">#24424</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.7...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `types-requests` from 2.32.4.20260324 to 2.33.0.20260408
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/python/typeshed/commits">compare view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-03 15:15:43 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0d0324ad3a chore(deps): bump the minor-and-patch group in /libs/checkpoint-sqlite with 2 updates (#7668)
Bumps the minor-and-patch group in /libs/checkpoint-sqlite with 2
updates: [ruff](https://github.com/astral-sh/ruff) and
[mypy](https://github.com/python/mypy).

Updates `ruff` from 0.15.8 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>0.15.11</h2>
<p>Released on 2026-04-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Ignore <code>RUF029</code> when function is
decorated with <code>asynccontextmanager</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24642">#24642</a>)</li>
<li>[<code>airflow</code>] Implement
<code>airflow-xcom-pull-in-template-string</code> (<code>AIR201</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/23583">#23583</a>)</li>
<li>[<code>flake8-bandit</code>] Fix <code>S103</code> false positives
and negatives in mask analysis (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24424">#24424</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.8...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `mypy` from 1.20.0 to 1.20.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's
changelog</a>.</em></p>
<blockquote>
<h3>Mypy 1.20.2</h3>
<ul>
<li>Use WAL with SQLite cache and fix close (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21154">21154</a>)</li>
<li>Adjust SQLite journal mode (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21217">21217</a>)</li>
<li>Correctly aggregate narrowing information on parent expressions
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21206">21206</a>)</li>
<li>Fix regression related to generic callables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21208">21208</a>)</li>
<li>Fix regression by avoiding widening types in some contexts
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21242">21242</a>)</li>
<li>Fix slicing in non-strict optional mode (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21282">21282</a>)</li>
<li>mypyc: Fix match statement semantics for &quot;or&quot; pattern
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21156">21156</a>)</li>
<li>mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR <a
href="https://redirect.github.com/python/mypy/pull/21275">21275</a>)</li>
<li>Initial support for Python 3.15.0a8 (Marc Mueller, PR <a
href="https://redirect.github.com/python/mypy/pull/21255">21255</a>)</li>
</ul>
<h3>Acknowledgements</h3>
<p>Thanks to all mypy contributors who contributed to this release:</p>
<ul>
<li>A5rocks</li>
<li>Aaron Wieczorek</li>
<li>Adam Turner</li>
<li>Ali Hamdan</li>
<li>asce</li>
<li>BobTheBuidler</li>
<li>Brent Westbrook</li>
<li>Brian Schubert</li>
<li>bzoracler</li>
<li>Chris Burroughs</li>
<li>Christoph Tyralla</li>
<li>Colin Watson</li>
<li>Donghoon Nam</li>
<li>E. M. Bray</li>
<li>Emma Smith</li>
<li>Ethan Sarp</li>
<li>George Ogden</li>
<li>getzze</li>
<li>grayjk</li>
<li>Gregor Riepl</li>
<li>Ivan Levkivskyi</li>
<li>James Hilliard</li>
<li>James Le Cuirot</li>
<li>Jeremy Nimmer</li>
<li>Joren Hammudoglu</li>
<li>Kai (Kazuya Ito)</li>
<li>kaushal trivedi</li>
<li>Kevin Kannammalil</li>
<li>Lukas Geiger</li>
<li>Łukasz Langa</li>
<li>Marc Mueller</li>
<li>Michael R. Crusoe</li>
<li>michaelm-openai</li>
<li>Neil Schemenauer</li>
<li>Piotr Sawicki</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/python/mypy/commit/145a062651b5f9996b75ef32b7040bd2e885ed82"><code>145a062</code></a>
Bump version to 1.20.2</li>
<li><a
href="https://github.com/python/mypy/commit/81cd49215c288eacb987de066f02daff2553b7c7"><code>81cd492</code></a>
Fix slicing with nonstrict optional (<a
href="https://redirect.github.com/python/mypy/issues/21282">#21282</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/908d3441eecbaa2a6193165317177db834d7ca1a"><code>908d344</code></a>
[mypyc] Set dunder attrs when adding module to sys.modules (<a
href="https://redirect.github.com/python/mypy/issues/21275">#21275</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/ba28610fac9d2b33be210ca8dcfe4bc47b7af424"><code>ba28610</code></a>
Initial support for Python 3.15.0a8 (<a
href="https://redirect.github.com/python/mypy/issues/21255">#21255</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/7b0e09f48dbd3717ed008a273cd17e8e960c2037"><code>7b0e09f</code></a>
Fix match statement semantics for &quot;or&quot; pattern (<a
href="https://redirect.github.com/python/mypy/issues/21156">#21156</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/92b74f226de62f7505f5ef5cb158e8ec9c58b8b7"><code>92b74f2</code></a>
Avoid widening types in conditional_types (<a
href="https://redirect.github.com/python/mypy/issues/21242">#21242</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/0dcbfaa40b0e360a16baea9cf851955375d91b54"><code>0dcbfaa</code></a>
Fix is_overlapping_types for generic callables (<a
href="https://redirect.github.com/python/mypy/issues/21208">#21208</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/210f518dede35292033ef0d387847406a0ccef8f"><code>210f518</code></a>
Correctly aggregate narrowing information on parent expressions (<a
href="https://redirect.github.com/python/mypy/issues/21206">#21206</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/c34530e53a10e385d8b0f1af4baa88a596b5ceaa"><code>c34530e</code></a>
Only set journal mode in coordinator (<a
href="https://redirect.github.com/python/mypy/issues/21217">#21217</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/79a3ec6d01b56a27c00e9b3320c2b1d4d73a77f9"><code>79a3ec6</code></a>
Use WAL with SQLite cache, fix close (<a
href="https://redirect.github.com/python/mypy/issues/21154">#21154</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/python/mypy/compare/v1.20.0...v1.20.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:16:12 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8593efe0ad chore(deps): bump the minor-and-patch group in /libs/checkpoint-postgres with 2 updates (#7665)
Bumps the minor-and-patch group in /libs/checkpoint-postgres with 2
updates: [ruff](https://github.com/astral-sh/ruff) and
[mypy](https://github.com/python/mypy).

Updates `ruff` from 0.15.8 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>0.15.11</h2>
<p>Released on 2026-04-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Ignore <code>RUF029</code> when function is
decorated with <code>asynccontextmanager</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24642">#24642</a>)</li>
<li>[<code>airflow</code>] Implement
<code>airflow-xcom-pull-in-template-string</code> (<code>AIR201</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/23583">#23583</a>)</li>
<li>[<code>flake8-bandit</code>] Fix <code>S103</code> false positives
and negatives in mask analysis (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24424">#24424</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.8...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `mypy` from 1.20.0 to 1.20.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's
changelog</a>.</em></p>
<blockquote>
<h3>Mypy 1.20.2</h3>
<ul>
<li>Use WAL with SQLite cache and fix close (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21154">21154</a>)</li>
<li>Adjust SQLite journal mode (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21217">21217</a>)</li>
<li>Correctly aggregate narrowing information on parent expressions
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21206">21206</a>)</li>
<li>Fix regression related to generic callables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21208">21208</a>)</li>
<li>Fix regression by avoiding widening types in some contexts
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21242">21242</a>)</li>
<li>Fix slicing in non-strict optional mode (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21282">21282</a>)</li>
<li>mypyc: Fix match statement semantics for &quot;or&quot; pattern
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21156">21156</a>)</li>
<li>mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR <a
href="https://redirect.github.com/python/mypy/pull/21275">21275</a>)</li>
<li>Initial support for Python 3.15.0a8 (Marc Mueller, PR <a
href="https://redirect.github.com/python/mypy/pull/21255">21255</a>)</li>
</ul>
<h3>Acknowledgements</h3>
<p>Thanks to all mypy contributors who contributed to this release:</p>
<ul>
<li>A5rocks</li>
<li>Aaron Wieczorek</li>
<li>Adam Turner</li>
<li>Ali Hamdan</li>
<li>asce</li>
<li>BobTheBuidler</li>
<li>Brent Westbrook</li>
<li>Brian Schubert</li>
<li>bzoracler</li>
<li>Chris Burroughs</li>
<li>Christoph Tyralla</li>
<li>Colin Watson</li>
<li>Donghoon Nam</li>
<li>E. M. Bray</li>
<li>Emma Smith</li>
<li>Ethan Sarp</li>
<li>George Ogden</li>
<li>getzze</li>
<li>grayjk</li>
<li>Gregor Riepl</li>
<li>Ivan Levkivskyi</li>
<li>James Hilliard</li>
<li>James Le Cuirot</li>
<li>Jeremy Nimmer</li>
<li>Joren Hammudoglu</li>
<li>Kai (Kazuya Ito)</li>
<li>kaushal trivedi</li>
<li>Kevin Kannammalil</li>
<li>Lukas Geiger</li>
<li>Łukasz Langa</li>
<li>Marc Mueller</li>
<li>Michael R. Crusoe</li>
<li>michaelm-openai</li>
<li>Neil Schemenauer</li>
<li>Piotr Sawicki</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/python/mypy/commit/145a062651b5f9996b75ef32b7040bd2e885ed82"><code>145a062</code></a>
Bump version to 1.20.2</li>
<li><a
href="https://github.com/python/mypy/commit/81cd49215c288eacb987de066f02daff2553b7c7"><code>81cd492</code></a>
Fix slicing with nonstrict optional (<a
href="https://redirect.github.com/python/mypy/issues/21282">#21282</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/908d3441eecbaa2a6193165317177db834d7ca1a"><code>908d344</code></a>
[mypyc] Set dunder attrs when adding module to sys.modules (<a
href="https://redirect.github.com/python/mypy/issues/21275">#21275</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/ba28610fac9d2b33be210ca8dcfe4bc47b7af424"><code>ba28610</code></a>
Initial support for Python 3.15.0a8 (<a
href="https://redirect.github.com/python/mypy/issues/21255">#21255</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/7b0e09f48dbd3717ed008a273cd17e8e960c2037"><code>7b0e09f</code></a>
Fix match statement semantics for &quot;or&quot; pattern (<a
href="https://redirect.github.com/python/mypy/issues/21156">#21156</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/92b74f226de62f7505f5ef5cb158e8ec9c58b8b7"><code>92b74f2</code></a>
Avoid widening types in conditional_types (<a
href="https://redirect.github.com/python/mypy/issues/21242">#21242</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/0dcbfaa40b0e360a16baea9cf851955375d91b54"><code>0dcbfaa</code></a>
Fix is_overlapping_types for generic callables (<a
href="https://redirect.github.com/python/mypy/issues/21208">#21208</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/210f518dede35292033ef0d387847406a0ccef8f"><code>210f518</code></a>
Correctly aggregate narrowing information on parent expressions (<a
href="https://redirect.github.com/python/mypy/issues/21206">#21206</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/c34530e53a10e385d8b0f1af4baa88a596b5ceaa"><code>c34530e</code></a>
Only set journal mode in coordinator (<a
href="https://redirect.github.com/python/mypy/issues/21217">#21217</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/79a3ec6d01b56a27c00e9b3320c2b1d4d73a77f9"><code>79a3ec6</code></a>
Use WAL with SQLite cache, fix close (<a
href="https://redirect.github.com/python/mypy/issues/21154">#21154</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/python/mypy/compare/v1.20.0...v1.20.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:15:49 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
402d767229 chore(deps): bump actions/upload-pages-artifact from 4 to 5 (#7664)
Bumps
[actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact)
from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-pages-artifact/releases">actions/upload-pages-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h1>Changelog</h1>
<ul>
<li>Update upload-artifact action to version 7 <a
href="https://github.com/Tom-van-Woudenberg"><code>@​Tom-van-Woudenberg</code></a>
(<a
href="https://redirect.github.com/actions/upload-pages-artifact/issues/139">#139</a>)</li>
<li>feat: add <code>include-hidden-files</code> input <a
href="https://github.com/jonchurch"><code>@​jonchurch</code></a> (<a
href="https://redirect.github.com/actions/upload-pages-artifact/issues/137">#137</a>)</li>
</ul>
<p>See details of <a
href="https://github.com/actions/upload-pages-artifact/compare/v4.0.0...v4.0.1">all
code changes</a> since previous release.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/fc324d3547104276b827a68afc52ff2a11cc49c9"><code>fc324d3</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-pages-artifact/issues/139">#139</a>
from Tom-van-Woudenberg/patch-1</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/fe9d4b7d84090e1d8d9c53a0236f810d4e00d2c3"><code>fe9d4b7</code></a>
Merge branch 'main' into patch-1</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/0ca16172ca884f0a37117fed41734f29784cc980"><code>0ca1617</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-pages-artifact/issues/137">#137</a>
from jonchurch/include-hidden-files</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/57f0e8492b437b7818227931fef2faa1a379839b"><code>57f0e84</code></a>
Update action.yml</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/4a90348b2933470dc78cec55534259872a6d3c0d"><code>4a90348</code></a>
v7 --&gt; hash</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/56f665a6f297fa95f8d735b314187fb2d7764569"><code>56f665a</code></a>
Update upload-artifact action to version 7</li>
<li><a
href="https://github.com/actions/upload-pages-artifact/commit/f7615f5917213b24245d49ba96693d0f5375a414"><code>f7615f5</code></a>
Add <code>include-hidden-files</code> input</li>
<li>See full diff in <a
href="https://github.com/actions/upload-pages-artifact/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-pages-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:14:40 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
95541cbf08 chore(deps): bump actions/github-script from 8 to 9 (#7663)
Bumps [actions/github-script](https://github.com/actions/github-script)
from 8 to 9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/github-script/releases">actions/github-script's
releases</a>.</em></p>
<blockquote>
<h2>v9.0.0</h2>
<p><strong>New features:</strong></p>
<ul>
<li><strong><code>getOctokit</code> factory function</strong> —
Available directly in the script context. Create additional
authenticated Octokit clients with different tokens for multi-token
workflows, GitHub App tokens, and cross-org access. See <a
href="https://github.com/actions/github-script#creating-additional-clients-with-getoctokit">Creating
additional clients with <code>getOctokit</code></a> for details and
examples.</li>
<li><strong>Orchestration ID in user-agent</strong> — The
<code>ACTIONS_ORCHESTRATION_ID</code> environment variable is
automatically appended to the user-agent string for request
tracing.</li>
</ul>
<p><strong>Breaking changes:</strong></p>
<ul>
<li><strong><code>require('@actions/github')</code> no longer works in
scripts.</strong> The upgrade to <code>@actions/github</code> v9
(ESM-only) means <code>require('@actions/github')</code> will fail at
runtime. If you previously used patterns like <code>const { getOctokit }
= require('@actions/github')</code> to create secondary clients, use the
new injected <code>getOctokit</code> function instead — it's available
directly in the script context with no imports needed.</li>
<li><code>getOctokit</code> is now an injected function parameter.
Scripts that declare <code>const getOctokit = ...</code> or <code>let
getOctokit = ...</code> will get a <code>SyntaxError</code> because
JavaScript does not allow <code>const</code>/<code>let</code>
redeclaration of function parameters. Use the injected
<code>getOctokit</code> directly, or use <code>var getOctokit =
...</code> if you need to redeclare it.</li>
<li>If your script accesses other <code>@actions/github</code> internals
beyond the standard <code>github</code>/<code>octokit</code> client, you
may need to update those references for v9 compatibility.</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Add ACTIONS_ORCHESTRATION_ID to user-agent string by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/695">actions/github-script#695</a></li>
<li>ci: use deployment: false for integration test environments by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/712">actions/github-script#712</a></li>
<li>feat!: add getOctokit to script context, upgrade
<code>@​actions/github</code> v9, <code>@​octokit/core</code> v7, and
related packages by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/github-script/pull/700">actions/github-script#700</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/github-script/pull/695">actions/github-script#695</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v8.0.0...v9.0.0">https://github.com/actions/github-script/compare/v8.0.0...v9.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/github-script/commit/3a2844b7e9c422d3c10d287c895573f7108da1b3"><code>3a2844b</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/700">#700</a>
from actions/salmanmkc/expose-getoctokit + prepare re...</li>
<li><a
href="https://github.com/actions/github-script/commit/ca10bbdd1a7739de09e99a200c7a59f5d73a4079"><code>ca10bbd</code></a>
fix: use <code>@​octokit/core/</code>types import for v7
compatibility</li>
<li><a
href="https://github.com/actions/github-script/commit/86e48e20ac85c970ed1f96e718fd068173948b7b"><code>86e48e2</code></a>
merge: incorporate main branch changes</li>
<li><a
href="https://github.com/actions/github-script/commit/c1084728b5b935ec4ddc1e4cee877b01797b3ff9"><code>c108472</code></a>
chore: rebuild dist for v9 upgrade and getOctokit factory</li>
<li><a
href="https://github.com/actions/github-script/commit/afff112e4f8b57c718168af75b89ce00bc8d091d"><code>afff112</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/github-script/issues/712">#712</a>
from actions/salmanmkc/deployment-false + fix user-ag...</li>
<li><a
href="https://github.com/actions/github-script/commit/ff8117e5b78c415f814f39ad6998f424fee7b817"><code>ff8117e</code></a>
ci: fix user-agent test to handle orchestration ID</li>
<li><a
href="https://github.com/actions/github-script/commit/81c6b7876079abe10ff715951c9fc7b3e1ab389d"><code>81c6b78</code></a>
ci: use deployment: false to suppress deployment noise from integration
tests</li>
<li><a
href="https://github.com/actions/github-script/commit/3953caf8858d318f37b6cc53a9f5708859b5a7b7"><code>3953caf</code></a>
docs: update README examples from <a
href="https://github.com/v8"><code>@​v8</code></a> to <a
href="https://github.com/v9"><code>@​v9</code></a>, add getOctokit docs
and v9 brea...</li>
<li><a
href="https://github.com/actions/github-script/commit/c17d55b90dcdb3d554d0027a6c180a7adc2daf78"><code>c17d55b</code></a>
ci: add getOctokit integration test job</li>
<li><a
href="https://github.com/actions/github-script/commit/a047196d9a02fe92098771cafbb98c2f1814e408"><code>a047196</code></a>
test: add getOctokit integration tests via callAsyncFunction</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/github-script/compare/v8...v9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=8&new-version=9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:14:30 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
30a1251dcc chore(deps): bump the minor-and-patch group with 3 updates (#7662)
Bumps the minor-and-patch group with 3 updates:
[docker/login-action](https://github.com/docker/login-action),
[pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish)
and
[peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request).

Updates `docker/login-action` from 4.0.0 to 4.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.0</h2>
<ul>
<li>Fix scoped Docker Hub cleanup path when registry is omitted by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/945">docker/login-action#945</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> and
<code>@​aws-sdk/client-ecr-public</code> to 3.1020.0 in <a
href="https://redirect.github.com/docker/login-action/pull/930">docker/login-action#930</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.77.0 to 0.86.0 in
<a
href="https://redirect.github.com/docker/login-action/pull/932">docker/login-action#932</a>
<a
href="https://redirect.github.com/docker/login-action/pull/936">docker/login-action#936</a></li>
<li>Bump brace-expansion from 1.1.12 to 1.1.13 in <a
href="https://redirect.github.com/docker/login-action/pull/952">docker/login-action#952</a></li>
<li>Bump fast-xml-parser from 5.3.4 to 5.3.6 in <a
href="https://redirect.github.com/docker/login-action/pull/942">docker/login-action#942</a></li>
<li>Bump flatted from 3.3.3 to 3.4.2 in <a
href="https://redirect.github.com/docker/login-action/pull/944">docker/login-action#944</a></li>
<li>Bump glob from 10.3.12 to 10.5.0 in <a
href="https://redirect.github.com/docker/login-action/pull/940">docker/login-action#940</a></li>
<li>Bump handlebars from 4.7.8 to 4.7.9 in <a
href="https://redirect.github.com/docker/login-action/pull/949">docker/login-action#949</a></li>
<li>Bump http-proxy-agent and https-proxy-agent to 8.0.0 in <a
href="https://redirect.github.com/docker/login-action/pull/937">docker/login-action#937</a></li>
<li>Bump lodash from 4.17.23 to 4.18.1 in <a
href="https://redirect.github.com/docker/login-action/pull/958">docker/login-action#958</a></li>
<li>Bump minimatch from 3.1.2 to 3.1.5 in <a
href="https://redirect.github.com/docker/login-action/pull/941">docker/login-action#941</a></li>
<li>Bump picomatch from 4.0.3 to 4.0.4 in <a
href="https://redirect.github.com/docker/login-action/pull/948">docker/login-action#948</a></li>
<li>Bump undici from 6.23.0 to 6.24.1 in <a
href="https://redirect.github.com/docker/login-action/pull/938">docker/login-action#938</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.0.0...v4.1.0">https://github.com/docker/login-action/compare/v4.0.0...v4.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/4907a6ddec9925e35a0a9e82d7399ccc52663121"><code>4907a6d</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/930">#930</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependenc...</li>
<li><a
href="https://github.com/docker/login-action/commit/1e233e691a8881d7f35ca7c2d5dfaaed80b39636"><code>1e233e6</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/6c24ead68057f18c30c808a431f0b85dc25663cb"><code>6c24ead</code></a>
build(deps): bump the aws-sdk-dependencies group with 2 updates</li>
<li><a
href="https://github.com/docker/login-action/commit/ee034d70944e3546349cd24295914f139342f1e6"><code>ee034d7</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/958">#958</a>
from docker/dependabot/npm_and_yarn/lodash-4.18.1</li>
<li><a
href="https://github.com/docker/login-action/commit/1527209db9734bd2352a2dc1a63d79c9aa5358bb"><code>1527209</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/937">#937</a>
from docker/dependabot/npm_and_yarn/proxy-agent-depen...</li>
<li><a
href="https://github.com/docker/login-action/commit/d39362aba4d72f8d9d93e0962119840690133e1b"><code>d39362a</code></a>
build(deps): bump lodash from 4.17.23 to 4.18.1</li>
<li><a
href="https://github.com/docker/login-action/commit/a6f092b568105cbb6d9deb7e55e0a4c5c1025fce"><code>a6f092b</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/60953f0bed2120ec69659d271fe18d34bc069779"><code>60953f0</code></a>
build(deps): bump the proxy-agent-dependencies group with 2 updates</li>
<li><a
href="https://github.com/docker/login-action/commit/62c688590fb4ab6c6e89a217ced0a7b2ddcf1340"><code>62c6885</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/936">#936</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/login-action/commit/102c0e672992d2e992c89b6f4808d65a353b5a1a"><code>102c0e6</code></a>
chore: update generated content</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/b45d80f862d83dbcd57f89517bcf500b2ab88fb2...4907a6ddec9925e35a0a9e82d7399ccc52663121">compare
view</a></li>
</ul>
</details>
<br />

Updates `pypa/gh-action-pypi-publish` from 1.13.0 to 1.14.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pypa/gh-action-pypi-publish/releases">pypa/gh-action-pypi-publish's
releases</a>.</em></p>
<blockquote>
<h2>v1.14.0</h2>
<!-- raw HTML omitted -->
<h2> What's Changed</h2>
<p>The main change in this release is that <code>verbose</code> and
<code>print-hash</code> inputs are now on by default. This was
contributed by <a
href="https://github.com/whitequark"><code>@​whitequark</code></a><a
href="https://github.com/sponsors/whitequark">💰</a> in <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/397">#397</a>.</p>
<h2>📝 Docs</h2>
<p><a href="https://github.com/woodruffw"><code>@​woodruffw</code></a><a
href="https://github.com/sponsors/woodruffw">💰</a> updated the mentions
of PEP 740 to stop implying that it might be experimental (it hasn't
been for quite a while!) in <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/388">#388</a>
and <a href="https://github.com/him2him2"><code>@​him2him2</code></a><a
href="https://github.com/sponsors/him2him2">💰</a> brushed up some
grammar in the README and SECURITY docs via <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/395">#395</a>.</p>
<h2>🛠️ Internal Updates</h2>
<p><a href="https://github.com/woodruffw"><code>@​woodruffw</code></a><a
href="https://github.com/sponsors/woodruffw">💰</a> bumped
<code>sigstore</code> and <code>pypi-attestations</code> in the lock
file (<a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/391">#391</a>)
and <a href="https://github.com/webknjaz"><code>@​webknjaz</code></a><a
href="https://github.com/sponsors/webknjaz">💰</a> added infra for using
type annotations in the project (<a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/381">#381</a>).</p>
<h2>💪 New Contributors</h2>
<ul>
<li><a href="https://github.com/him2him2"><code>@​him2him2</code></a>
made their first contribution in <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/395">#395</a></li>
<li><a
href="https://github.com/whitequark"><code>@​whitequark</code></a> made
their first contribution in <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/397">#397</a></li>
</ul>
<p><strong>🪞 Full Diff</strong>: <a
href="https://github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...v1.14.0">https://github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...v1.14.0</a></p>
<p><strong>🧔‍♂️ Release Manager:</strong> <a
href="https://github.com/sponsors/webknjaz"><code>@​webknjaz</code></a>
<a href="https://stand-with-ukraine.pp.ua">🇺🇦</a></p>
<p><strong>🙏 Special Thanks</strong> to <a
href="https://github.com/facutuesca"><code>@​facutuesca</code></a><a
href="https://github.com/sponsors/facutuesca">💰</a> and <a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a><a
href="https://github.com/sponsors/woodruffw">💰</a> for helping maintain
this project when <a href="https://github.com/sponsors/webknjaz">I</a>
can't!</p>
<p><strong>💬 Discuss</strong> <a
href="https://bsky.app/profile/webknjaz.me/post/3mivwsz3qzk2e">on
Bluesky 🦋</a>, <a
href="https://mastodon.social/@webknjaz/116363779997051422">on Mastodon
🐘</a> and <a
href="https://github.com/pypa/gh-action-pypi-publish/discussions/404">on
GitHub</a>.</p>
<p><a href="https://github.com/sponsors/webknjaz"><img
src="https://img.shields.io/badge/%40webknjaz-transparent?logo=githubsponsors&amp;logoColor=%23EA4AAA&amp;label=Sponsor&amp;color=2a313c"
alt="GH Sponsors badge" /></a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/cef221092ed1bacb1cc03d23a2d87d1d172e277b"><code>cef2210</code></a>
Merge pull request <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/397">#397</a>
from whitequark/patch-1</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/b4595e2555a031e2fd6f0bbded4e7918eaa2724e"><code>b4595e2</code></a>
Enable <code>verbose</code> and <code>print-hash</code> by default.</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/e2bab26859796ee5c3bf97b8f394ce1e6570e906"><code>e2bab26</code></a>
Merge pull request <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/395">#395</a>
from him2him2/docs/fix-typos-and-grammar</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/7495c384ec7a0240a28e568e7ffc60af1629585d"><code>7495c38</code></a>
docs: fix typos and grammar in README and SECURITY</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/03f86fee9ac21f854951f5c6e2a02c2a1324aec7"><code>03f86fe</code></a>
Merge pull request <a
href="https://redirect.github.com/pypa/gh-action-pypi-publish/issues/388">#388</a>
from woodruffw-forks/ww/rm-experimental</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/4c78f1c53c55c528d8abd83df933ae92bd4c1d8c"><code>4c78f1c</code></a>
Merge branch 'unstable/v1' into ww/rm-experimental</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/b5a6e8ba2611ad0c810f383eed9e6629eb0b3b2f"><code>b5a6e8b</code></a>
deps: bump sigstore and pypi-attestations</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/a48a03e758da35722b0d159dae23e0440d0fcce2"><code>a48a03e</code></a>
remove another experimental mention</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/8087a88a46924f78608905d7841a170e749524ce"><code>8087a88</code></a>
action: remove a lingering mention of PEP 740 being experimental</li>
<li><a
href="https://github.com/pypa/gh-action-pypi-publish/commit/3317ede93a4981d0fc490510c6fcf8bf0e92ed05"><code>3317ede</code></a>
🧪 Integrate actionlint via pre-commit framework</li>
<li>Additional commits viewable in <a
href="https://github.com/pypa/gh-action-pypi-publish/compare/ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e...cef221092ed1bacb1cc03d23a2d87d1d172e277b">compare
view</a></li>
</ul>
</details>
<br />

Updates `peter-evans/create-pull-request` from 8.1.0 to 8.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/peter-evans/create-pull-request/releases">peter-evans/create-pull-request's
releases</a>.</em></p>
<blockquote>
<h2>Create Pull Request v8.1.1</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps-dev): bump the npm group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4305">peter-evans/create-pull-request#4305</a></li>
<li>build(deps): bump minimatch by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4311">peter-evans/create-pull-request#4311</a></li>
<li>build(deps): bump the github-actions group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4316">peter-evans/create-pull-request#4316</a></li>
<li>build(deps): bump <code>@​tootallnate/once</code> and
jest-environment-jsdom by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4323">peter-evans/create-pull-request#4323</a></li>
<li>build(deps-dev): bump undici from 6.23.0 to 6.24.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4328">peter-evans/create-pull-request#4328</a></li>
<li>build(deps-dev): bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4334">peter-evans/create-pull-request#4334</a></li>
<li>build(deps): bump picomatch by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4339">peter-evans/create-pull-request#4339</a></li>
<li>build(deps-dev): bump handlebars from 4.7.8 to 4.7.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4344">peter-evans/create-pull-request#4344</a></li>
<li>build(deps-dev): bump the npm group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4349">peter-evans/create-pull-request#4349</a></li>
<li>fix: retry post-creation API calls on 422 eventual consistency
errors by <a
href="https://github.com/peter-evans"><code>@​peter-evans</code></a> in
<a
href="https://redirect.github.com/peter-evans/create-pull-request/pull/4356">peter-evans/create-pull-request#4356</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/peter-evans/create-pull-request/compare/v8.1.0...v8.1.1">https://github.com/peter-evans/create-pull-request/compare/v8.1.0...v8.1.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/5f6978faf089d4d20b00c7766989d076bb2fc7f1"><code>5f6978f</code></a>
fix: retry post-creation API calls on 422 eventual consistency errors
(<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4356">#4356</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/d32e88dac789dcc7906e7d26f69f24116fa9c97d"><code>d32e88d</code></a>
build(deps-dev): bump the npm group with 3 updates (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4349">#4349</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/8170bccad11c0df62542c04dcaefe36d342dfd39"><code>8170bcc</code></a>
build(deps-dev): bump handlebars from 4.7.8 to 4.7.9 (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4344">#4344</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/00418193b417f888dbf1d993c5c0d31d27fdc7de"><code>0041819</code></a>
build(deps): bump picomatch (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4339">#4339</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/b993918c8536b6d44706130734d5456879762b27"><code>b993918</code></a>
build(deps-dev): bump flatted from 3.3.1 to 3.4.2 (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4334">#4334</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/36d7c8468b48f9c2f8f29e260e82f10d4b90d2bd"><code>36d7c84</code></a>
build(deps-dev): bump undici from 6.23.0 to 6.24.0 (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4328">#4328</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/a45d1fb447fcaf601166e405fd4f335cde1a8aa8"><code>a45d1fb</code></a>
build(deps): bump <code>@​tootallnate/once</code> and
jest-environment-jsdom (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4323">#4323</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/3499eb61835cc0015c0b786e203d74b1e8f55e43"><code>3499eb6</code></a>
build(deps): bump the github-actions group with 2 updates (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4316">#4316</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/3f3b473b8c148f5a7520efb4d1f9a70eea3d9d1f"><code>3f3b473</code></a>
build(deps): bump minimatch (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4311">#4311</a>)</li>
<li><a
href="https://github.com/peter-evans/create-pull-request/commit/6699836a213cf8b28c4f0408a404a6ac79d4458a"><code>6699836</code></a>
build(deps-dev): bump the npm group with 2 updates (<a
href="https://redirect.github.com/peter-evans/create-pull-request/issues/4305">#4305</a>)</li>
<li>See full diff in <a
href="https://github.com/peter-evans/create-pull-request/compare/c0f553fe549906ede9cf27b5156039d195d2ece0...5f6978faf089d4d20b00c7766989d076bb2fc7f1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:14:22 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1a80cb3eab chore(deps): bump the minor-and-patch group in /libs/cli with 4 updates (#7674)
Bumps the minor-and-patch group in /libs/cli with 4 updates:
[click](https://github.com/pallets/click),
[pathspec](https://github.com/cpburnz/python-pathspec),
[ruff](https://github.com/astral-sh/ruff) and
[mypy](https://github.com/python/mypy).

Updates `click` from 8.3.2 to 8.3.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/click/releases">click's
releases</a>.</em></p>
<blockquote>
<h2>8.3.3</h2>
<p>This is the Click 8.3.3 fix release, which fixes bugs but does not
otherwise change behavior and should not result in breaking changes
compared to the latest feature release.</p>
<p>PyPI: <a
href="https://pypi.org/project/click/8.3.3/">https://pypi.org/project/click/8.3.3/</a>
Changes: <a
href="https://click.palletsprojects.com/page/changes/#version-8-3-3">https://click.palletsprojects.com/page/changes/#version-8-3-3</a>
Milestone: <a
href="https://github.com/pallets/click/milestone/30">https://github.com/pallets/click/milestone/30</a></p>
<ul>
<li>Use :func:<code>shlex.split</code> to split pager and editor
commands into <code>argv</code>
lists for :class:<code>subprocess.Popen</code>, removing
<code>shell=True</code>.
<a
href="https://redirect.github.com/pallets/click/issues/1026">#1026</a>
<a
href="https://redirect.github.com/pallets/click/issues/1477">#1477</a>
<a
href="https://redirect.github.com/pallets/click/issues/2775">#2775</a></li>
<li>Fix <code>TypeError</code> when rendering help for an option whose
default value is
an object that doesn't support equality comparison with strings, such as
<code>semver.Version</code>. <a
href="https://redirect.github.com/pallets/click/issues/3298">#3298</a>
<a
href="https://redirect.github.com/pallets/click/issues/3299">#3299</a></li>
<li>Fix pager test pollution under parallel execution by using pytest's
<code>tmp_path</code> fixture instead of a shared temporary file path.
<a
href="https://redirect.github.com/pallets/click/issues/3238">#3238</a></li>
<li>Treat <code>Sentinel.UNSET</code> values in a
<code>default_map</code> as absent, so they fall
through to the next default source instead of being used as the value.
<a
href="https://redirect.github.com/pallets/click/issues/3224">#3224</a>
<a
href="https://redirect.github.com/pallets/click/issues/3240">#3240</a></li>
<li>Patch <code>pdb.Pdb</code> in <code>CliRunner</code> isolation so
<code>pdb.set_trace()</code>,
<code>breakpoint()</code>, and debuggers subclassing
<code>pdb.Pdb</code> (ipdb, pdbpp) can
interact with the real terminal instead of the captured I/O streams.
<a href="https://redirect.github.com/pallets/click/issues/654">#654</a>
<a href="https://redirect.github.com/pallets/click/issues/824">#824</a>
<a href="https://redirect.github.com/pallets/click/issues/843">#843</a>
<a href="https://redirect.github.com/pallets/click/issues/951">#951</a>
<a
href="https://redirect.github.com/pallets/click/issues/3235">#3235</a></li>
<li>Add optional randomized parallel test execution using
<code>pytest-randomly</code> and
<code>pytest-xdist</code> to detect test pollution and race conditions.
<a
href="https://redirect.github.com/pallets/click/issues/3151">#3151</a></li>
<li>Add contributor documentation for running stress tests, randomized
parallel tests, and Flask smoke tests. <a
href="https://redirect.github.com/pallets/click/issues/3151">#3151</a>
<a
href="https://redirect.github.com/pallets/click/issues/3177">#3177</a></li>
<li>Show custom <code>show_default</code> string in prompts, matching
the existing
help text behavior. <a
href="https://redirect.github.com/pallets/click/issues/2836">#2836</a>
<a
href="https://redirect.github.com/pallets/click/issues/2837">#2837</a>
<a
href="https://redirect.github.com/pallets/click/issues/3165">#3165</a>
<a
href="https://redirect.github.com/pallets/click/issues/3262">#3262</a>
<a
href="https://redirect.github.com/pallets/click/issues/3280">#3280</a>
<a
href="https://redirect.github.com/pallets/click/issues/3328">#3328</a></li>
<li>Fix <code>default=True</code> with boolean <code>flag_value</code>
always returning the
<code>flag_value</code> instead of <code>True</code>. The
<code>default=True</code> to <code>flag_value</code>
substitution now only applies to non-boolean flags, where
<code>True</code> acts as a
sentinel meaning &quot;activate this flag by default&quot;. For boolean
flags,
<code>default=True</code> is returned as a literal value. <a
href="https://redirect.github.com/pallets/click/issues/3111">#3111</a>
<a
href="https://redirect.github.com/pallets/click/issues/3239">#3239</a></li>
<li>Mark <code>make_default_short_help</code> as private API. <a
href="https://redirect.github.com/pallets/click/issues/3189">#3189</a>
<a
href="https://redirect.github.com/pallets/click/issues/3250">#3250</a></li>
<li><code>CliRunner</code>'s redirected streams now expose the original
file descriptor
via <code>fileno()</code>, so that <code>faulthandler</code>,
<code>subprocess</code>, and other
C-level consumers no longer crash with
<code>io.UnsupportedOperation</code>.
<a
href="https://redirect.github.com/pallets/click/issues/2865">#2865</a></li>
<li>Change :class:<code>ParameterSource</code> to an
:class:<code>~enum.IntEnum</code> and reorder
its members from most to least explicit, so values can be compared to
check whether a parameter was explicitly provided. <a
href="https://redirect.github.com/pallets/click/issues/2879">#2879</a>
<a
href="https://redirect.github.com/pallets/click/issues/3248">#3248</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pallets/click/blob/main/CHANGES.rst">click's
changelog</a>.</em></p>
<blockquote>
<h2>Version 8.3.3</h2>
<p>Released 2026-04-20</p>
<ul>
<li>Use :func:<code>shlex.split</code> to split pager and editor
commands into <code>argv</code>
lists for :class:<code>subprocess.Popen</code>, removing
<code>shell=True</code>.
:issue:<code>1026</code> :pr:<code>1477</code>
:pr:<code>2775</code></li>
<li>Fix <code>TypeError</code> when rendering help for an option whose
default value is
an object that doesn't support equality comparison with strings, such as
<code>semver.Version</code>. :issue:<code>3298</code>
:pr:<code>3299</code></li>
<li>Fix pager test pollution under parallel execution by using pytest's
<code>tmp_path</code> fixture instead of a shared temporary file path.
:pr:<code>3238</code></li>
<li>Treat <code>Sentinel.UNSET</code> values in a
<code>default_map</code> as absent, so they fall
through to the next default source instead of being used as the value.
:issue:<code>3224</code> :pr:<code>3240</code></li>
<li>Patch <code>pdb.Pdb</code> in <code>CliRunner</code> isolation so
<code>pdb.set_trace()</code>,
<code>breakpoint()</code>, and debuggers subclassing
<code>pdb.Pdb</code> (ipdb, pdbpp) can
interact with the real terminal instead of the captured I/O streams.
:issue:<code>654</code> :issue:<code>824</code> :issue:<code>843</code>
:pr:<code>951</code> :pr:<code>3235</code></li>
<li>Add optional randomized parallel test execution using
<code>pytest-randomly</code> and
<code>pytest-xdist</code> to detect test pollution and race conditions.
:pr:<code>3151</code></li>
<li>Add contributor documentation for running stress tests, randomized
parallel tests, and Flask smoke tests. :pr:<code>3151</code>
:pr:<code>3177</code></li>
<li>Show custom <code>show_default</code> string in prompts, matching
the existing
help text behavior. :issue:<code>2836</code> :pr:<code>2837</code>
:pr:<code>3165</code> :pr:<code>3262</code> :pr:<code>3280</code>
:pr:<code>3328</code></li>
<li>Fix <code>default=True</code> with boolean <code>flag_value</code>
always returning the
<code>flag_value</code> instead of <code>True</code>. The
<code>default=True</code> to <code>flag_value</code>
substitution now only applies to non-boolean flags, where
<code>True</code> acts as a
sentinel meaning &quot;activate this flag by default&quot;. For boolean
flags,
<code>default=True</code> is returned as a literal value.
:issue:<code>3111</code> :pr:<code>3239</code></li>
<li>Mark <code>make_default_short_help</code> as private API.
:issue:<code>3189</code> :pr:<code>3250</code></li>
<li><code>CliRunner</code>'s redirected streams now expose the original
file descriptor
via <code>fileno()</code>, so that <code>faulthandler</code>,
<code>subprocess</code>, and other
C-level consumers no longer crash with
<code>io.UnsupportedOperation</code>.
:issue:<code>2865</code></li>
<li>Change :class:<code>ParameterSource</code> to an
:class:<code>~enum.IntEnum</code> and reorder
its members from most to least explicit, so values can be compared to
check whether a parameter was explicitly provided.
:issue:<code>2879</code> :pr:<code>3248</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pallets/click/commit/c06d2d0a6aee6bcc50bd8257be2a4a592f4e75d0"><code>c06d2d0</code></a>
Release 8.3.3</li>
<li><a
href="https://github.com/pallets/click/commit/f1f191ecd2c790b161187c78e7c88440e9524e5c"><code>f1f191e</code></a>
Apply format guidelines to commits since latest 8.3.2 release (<a
href="https://redirect.github.com/pallets/click/issues/3343">#3343</a>)</li>
<li><a
href="https://github.com/pallets/click/commit/bb59ba0fd279ca085d1113f0499b6a602ca31081"><code>bb59ba0</code></a>
Apply format guidelines to commits since latest 8.3.2 release</li>
<li><a
href="https://github.com/pallets/click/commit/4a352253c9ff013e36d11e4a6820d36d00ff2cd4"><code>4a35225</code></a>
Reduce blast-radius of <code>UNSET</code> in <code>default_map</code>
(<a
href="https://redirect.github.com/pallets/click/issues/3240">#3240</a>)</li>
<li><a
href="https://github.com/pallets/click/commit/c07bb936de43fd303f9cfbefe248ab23fd2199c8"><code>c07bb93</code></a>
Merge branch 'stable' into unset-in-default-map</li>
<li><a
href="https://github.com/pallets/click/commit/c7e1ba8448cbcb2cdd9c1c7f4a592e863dcc3995"><code>c7e1ba8</code></a>
Reorder <code>ParameterSource</code> (<a
href="https://redirect.github.com/pallets/click/issues/3248">#3248</a>)</li>
<li><a
href="https://github.com/pallets/click/commit/76552ff1e8c85837f911fc34037e702ae4327eda"><code>76552ff</code></a>
Show default string in prompt (<a
href="https://redirect.github.com/pallets/click/issues/3328">#3328</a>)</li>
<li><a
href="https://github.com/pallets/click/commit/ac5cec5fe54e5a691e7bac17f441ce9498e0744c"><code>ac5cec5</code></a>
Reorder ParameterSource from most to least explicit</li>
<li><a
href="https://github.com/pallets/click/commit/8c452e00e6772931b7071d9316b82b77e5b8f280"><code>8c452e0</code></a>
Merge branch 'stable' into show-default-string-in-prompt</li>
<li><a
href="https://github.com/pallets/click/commit/8c95c73bd5ef89eac638f85f1904a104ba4b1a32"><code>8c95c73</code></a>
Reconcile default value passing and default activation (<a
href="https://redirect.github.com/pallets/click/issues/3239">#3239</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pallets/click/compare/8.3.2...8.3.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `pathspec` from 1.0.4 to 1.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cpburnz/python-pathspec/releases">pathspec's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.1</h2>
<p>Release v1.1.1. See <a
href="https://github.com/cpburnz/python-pathspec/blob/v1.1.1/CHANGES.rst">CHANGES.rst</a>.</p>
<h2>v1.1.0</h2>
<p>Release v1.1.0. See <a
href="https://github.com/cpburnz/python-pathspec/blob/v1.1.0/CHANGES.rst">CHANGES.rst</a>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/cpburnz/python-pathspec/blob/master/CHANGES.rst">pathspec's
changelog</a>.</em></p>
<blockquote>
<h2>1.1.1 (2026-04-26)</h2>
<p>Improvements:</p>
<ul>
<li>Improved type checking with mypy and pyright.</li>
</ul>
<p>Bug fixes:</p>
<ul>
<li>Fixed typing on <code>PathSpec[TPattern]</code> to
<code>PathSpec[TPattern_co]</code>.</li>
<li>Added missing variant type-hint <code>type[Pattern]</code> to
<code>PathSpec.from_lines()</code> parameter
<code>pattern_factory</code>.</li>
<li>Fixed possible type error when using <code>+</code> and
<code>+=</code> operators on <code>PathSpec</code>.</li>
</ul>
<h2>1.1.0 (2026-04-22)</h2>
<p>New features:</p>
<ul>
<li><code>Issue
[#108](https://github.com/cpburnz/python-pathspec/issues/108)</code>_:
Specialize pattern type for <code>PathSpec</code> as
<code>PathSpec[TPattern]</code> for better debugging of
<code>PathSpec().patterns</code>.</li>
</ul>
<p>Bug fixes:</p>
<ul>
<li><code>Issue
[#93](https://github.com/cpburnz/python-pathspec/issues/93)</code>_: Git
discards invalid range notation. <code>GitIgnoreSpecPattern</code> now
discards patterns with invalid range notation like Git.</li>
<li><code>Pull
[#106](https://github.com/cpburnz/python-pathspec/issues/106)</code>_:
Fix escape() not escaping backslash characters.</li>
</ul>
<p>Improvements:</p>
<ul>
<li><code>Pull
[#110](https://github.com/cpburnz/python-pathspec/issues/110)</code>_:
Nicer debug print outs (and str for regex pattern).</li>
</ul>
<p>.. _<code>Pull
[#106](https://github.com/cpburnz/python-pathspec/issues/106)</code>: <a
href="https://redirect.github.com/cpburnz/python-pathspec/pull/106">cpburnz/python-pathspec#106</a>
.. _<code>Issue
[#108](https://github.com/cpburnz/python-pathspec/issues/108)</code>: <a
href="https://redirect.github.com/cpburnz/python-pathspec/issues/108">cpburnz/python-pathspec#108</a>
.. _<code>Pull
[#110](https://github.com/cpburnz/python-pathspec/issues/110)</code>: <a
href="https://redirect.github.com/cpburnz/python-pathspec/pull/110">cpburnz/python-pathspec#110</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/ecf71a99ca739479d450b9830f43416ea0c519c7"><code>ecf71a9</code></a>
Release v1.1.1</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/6727491ff877e570e450b078c345d9346db7e531"><code>6727491</code></a>
Improve type checking with mypy and pyright</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/c9249c8b4ca165ca8c5eea191cea4c0e6f3aa827"><code>c9249c8</code></a>
Release v1.1.0</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/a1abeba97f1fdbc3bc0e64e6c4d7ee9b63c4cf77"><code>a1abeba</code></a>
Test Iron Proxy for CI</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/0b04daeafaea8c82a6fa3e86090061dc47c61ea6"><code>0b04dae</code></a>
Test Iron Proxy for CI</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/ccaedca31c5cd904c5bb55df0f0045c675f77b7f"><code>ccaedca</code></a>
Test Iron Proxy for CI</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/06391d861d68ba4763e8c377c8bb1b9392bcc76a"><code>06391d8</code></a>
Test Iron Proxy for CI</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/45907bf50a5cabe525306b99e85779639d9ca55e"><code>45907bf</code></a>
Test Iron Proxy for CI</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/0d7c7deb138050c8586000682134d820a176bc10"><code>0d7c7de</code></a>
Pin all Github actions</li>
<li><a
href="https://github.com/cpburnz/python-pathspec/commit/36faddae807a997d04ccfc8cf00931819464260c"><code>36fadda</code></a>
Specialize patterns</li>
<li>Additional commits viewable in <a
href="https://github.com/cpburnz/python-pathspec/compare/v1.0.4...v1.1.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.11 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.11...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `mypy` from 1.20.1 to 1.20.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's
changelog</a>.</em></p>
<blockquote>
<h3>Mypy 1.20.1</h3>
<ul>
<li>Always disable sync in SQLite cache (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21184">21184</a>)</li>
<li>Temporarily skip few base64 tests (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21193">21193</a>)</li>
<li>Revert <code>dict.__or__</code> typeshed change (Ivan Levkivskyi, PR
<a
href="https://redirect.github.com/python/mypy/pull/21186">21186</a>)</li>
<li>Fix narrowing for match case with variadic tuples (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21192">21192</a>)</li>
<li>Avoid narrowing <code>type[T]</code> in type calls (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21174">21174</a>)</li>
<li>Fix regression for catching empty tuple in except (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21153">21153</a>)</li>
<li>Fix reachability for frozenset and dict view narrowing (Shantanu, PR
<a
href="https://redirect.github.com/python/mypy/pull/21151">21151</a>)</li>
<li>Fix narrowing with chained comparison (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21150">21150</a>)</li>
<li>Avoid narrowing to unreachable at module level (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21144">21144</a>)</li>
<li>Allow dangerous identity comparisons to <code>Any</code> typed
variables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21142">21142</a>)</li>
<li><code>--warn-unused-config</code> should not be a strict flag (Ivan
Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21139">21139</a>)</li>
</ul>
<h3>Mypy 1.20.2</h3>
<ul>
<li>Use WAL with SQLite cache and fix close (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21154">21154</a>)</li>
<li>Adjust SQLite journal mode (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21217">21217</a>)</li>
<li>Correctly aggregate narrowing information on parent expressions
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21206">21206</a>)</li>
<li>Fix regression related to generic callables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21208">21208</a>)</li>
<li>Fix regression by avoiding widening types in some contexts
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21242">21242</a>)</li>
<li>Fix slicing in non-strict optional mode (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21282">21282</a>)</li>
<li>mypyc: Fix match statement semantics for &quot;or&quot; pattern
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21156">21156</a>)</li>
<li>mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR <a
href="https://redirect.github.com/python/mypy/pull/21275">21275</a>)</li>
<li>Initial support for Python 3.15.0a8 (Marc Mueller, PR <a
href="https://redirect.github.com/python/mypy/pull/21255">21255</a>)</li>
</ul>
<h3>Acknowledgements</h3>
<p>Thanks to all mypy contributors who contributed to this release:</p>
<ul>
<li>A5rocks</li>
<li>Aaron Wieczorek</li>
<li>Adam Turner</li>
<li>Ali Hamdan</li>
<li>asce</li>
<li>BobTheBuidler</li>
<li>Brent Westbrook</li>
<li>Brian Schubert</li>
<li>bzoracler</li>
<li>Chris Burroughs</li>
<li>Christoph Tyralla</li>
<li>Colin Watson</li>
<li>Donghoon Nam</li>
<li>E. M. Bray</li>
<li>Emma Smith</li>
<li>Ethan Sarp</li>
<li>George Ogden</li>
<li>getzze</li>
<li>grayjk</li>
<li>Gregor Riepl</li>
<li>Ivan Levkivskyi</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/python/mypy/commit/145a062651b5f9996b75ef32b7040bd2e885ed82"><code>145a062</code></a>
Bump version to 1.20.2</li>
<li><a
href="https://github.com/python/mypy/commit/81cd49215c288eacb987de066f02daff2553b7c7"><code>81cd492</code></a>
Fix slicing with nonstrict optional (<a
href="https://redirect.github.com/python/mypy/issues/21282">#21282</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/908d3441eecbaa2a6193165317177db834d7ca1a"><code>908d344</code></a>
[mypyc] Set dunder attrs when adding module to sys.modules (<a
href="https://redirect.github.com/python/mypy/issues/21275">#21275</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/ba28610fac9d2b33be210ca8dcfe4bc47b7af424"><code>ba28610</code></a>
Initial support for Python 3.15.0a8 (<a
href="https://redirect.github.com/python/mypy/issues/21255">#21255</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/7b0e09f48dbd3717ed008a273cd17e8e960c2037"><code>7b0e09f</code></a>
Fix match statement semantics for &quot;or&quot; pattern (<a
href="https://redirect.github.com/python/mypy/issues/21156">#21156</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/92b74f226de62f7505f5ef5cb158e8ec9c58b8b7"><code>92b74f2</code></a>
Avoid widening types in conditional_types (<a
href="https://redirect.github.com/python/mypy/issues/21242">#21242</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/0dcbfaa40b0e360a16baea9cf851955375d91b54"><code>0dcbfaa</code></a>
Fix is_overlapping_types for generic callables (<a
href="https://redirect.github.com/python/mypy/issues/21208">#21208</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/210f518dede35292033ef0d387847406a0ccef8f"><code>210f518</code></a>
Correctly aggregate narrowing information on parent expressions (<a
href="https://redirect.github.com/python/mypy/issues/21206">#21206</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/c34530e53a10e385d8b0f1af4baa88a596b5ceaa"><code>c34530e</code></a>
Only set journal mode in coordinator (<a
href="https://redirect.github.com/python/mypy/issues/21217">#21217</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/79a3ec6d01b56a27c00e9b3320c2b1d4d73a77f9"><code>79a3ec6</code></a>
Use WAL with SQLite cache, fix close (<a
href="https://redirect.github.com/python/mypy/issues/21154">#21154</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/python/mypy/compare/v1.20.1...v1.20.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:14:02 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
60135677dd chore(deps): bump the minor-and-patch group in /libs/cli/js-examples with 8 updates (#7673)
Bumps the minor-and-patch group in /libs/cli/js-examples with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) |
`1.1.38` | `1.1.42` |
|
[@langchain/langgraph](https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core)
| `1.2.6` | `1.2.9` |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.58.0` | `8.59.1` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.58.0` | `8.59.1` |
| [dotenv](https://github.com/motdotla/dotenv) | `17.3.1` | `17.4.2` |
| [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.2.1` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.3` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.6` | `29.4.9`
|

Updates `@langchain/core` from 1.1.38 to 1.1.42
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchainjs/releases"><code>@​langchain/core</code>'s
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.41</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10733">#10733</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/589f29ce844eb252c2d5e6b0f8d26de37763a0d7"><code>589f29c</code></a>
Thanks <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>! -
fix(core): Update inheritance behavior for tracer metadata for special
keys</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10711">#10711</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/2e9e6969e248a53ede0659a41d0ac8dbaf291ab4"><code>2e9e696</code></a>
Thanks <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>! -
feat(core): Add chat model and llm invocation params to traced
metadata</p>
</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.40</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10694">#10694</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/d3e080995bb267bf3797067ab53c96bc2a6c8e3f"><code>d3e0809</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
refactor(core): decouple tracer-only metadata defaults from runnable
metadata
<ul>
<li>Add tracer-scoped inheritable metadata/tag options in callback
manager while keeping backward-compatible aliases.</li>
<li>Move configurable-to-tracing metadata derivation into a tracer-only
path and keep <code>ensureConfig</code> metadata mirroring limited to
<code>model</code>.</li>
<li>Update <code>LangChainTracer</code> default metadata/tag handling
and add regression tests for stream events metadata behavior.</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.39</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10430">#10430</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/d3d0922c24afcd3006fb94dcadd3ebe08fbf2383"><code>d3d0922</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(langchain): support for browser tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langchainjs/commits">compare
view</a></li>
</ul>
</details>
<br />

Updates `@langchain/langgraph` from 1.2.6 to 1.2.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/releases"><code>@​langchain/langgraph</code>'s
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2315">#2315</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
propagate tracer metadata defaults from configurable</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2311">#2311</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
fix: export missing types for typescript 6.0 declaration file
compatibility</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/458d66bf665468854abb8133594d4d4f966054ed"><code>458d66b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.8.9</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2275">#2275</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
enhance runtime with executionInfo and serverInfo</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2281">#2281</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(sdk): support for headless tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/CHANGELOG.md"><code>@​langchain/langgraph</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>1.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2315">#2315</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
propagate tracer metadata defaults from configurable</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2311">#2311</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
fix: export missing types for typescript 6.0 declaration file
compatibility</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/458d66bf665468854abb8133594d4d4f966054ed"><code>458d66b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.8.9</li>
</ul>
</li>
</ul>
<h2>1.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2275">#2275</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
enhance runtime with executionInfo and serverInfo</li>
</ul>
<h2>1.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2281">#2281</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(sdk): support for headless tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/aba243a403ffc0795ea95693b88741465e78abec"><code>aba243a</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2313">#2313</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
fix(langgraph): propagate tracer metadata defaults from configurable (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2315">#2315</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
fix: export missing types for typescript 6.0 declaration file
compatibility (...</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/9e9807523f751ddcbad8023706563a24806ef50e"><code>9e98075</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2294">#2294</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
feat: enhance runtime with executionInfo and serverInfo (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2275">#2275</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/ed1107df54760e7c8186911064b740d3231398a4"><code>ed1107d</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2283">#2283</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
feat(sdk): support for headless tools (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2281">#2281</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/05810fe087e1c23d41d3cc495985c8eb91542c83"><code>05810fe</code></a>
Fix Vue subagent grouping during streaming (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2274">#2274</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/271f9750f0aaaac9ead036d997797b174ec9743c"><code>271f975</code></a>
chore(deps): bump the langchain group across 1 directory with 7 updates
(<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2270">#2270</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/d88f29ba2558579781e60307947dba47290d58a1"><code>d88f29b</code></a>
chore(repo): migrate linting and formatting from ESLint/Prettier to
oxlint/ox...</li>
<li>See full diff in <a
href="https://github.com/langchain-ai/langgraphjs/commits/@langchain/langgraph@1.2.9/libs/langgraph-core">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/eslint-plugin` from 8.58.0 to 8.59.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases"><code>@​typescript-eslint/eslint-plugin</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v8.59.1</h2>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.59.0</h2>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.58.2</h2>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md"><code>@​typescript-eslint/eslint-plugin</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2">GitHub
Releases</a> for more information.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/52457932e5507b5ca01e720a541f3f8d01e09b9d"><code>5245793</code></a>
chore(release): publish 8.59.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/3cef12471659c42c9d4b179f4113a080b1fa2d66"><code>3cef124</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in dot...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/27c507b6e532abcd1efa51055f045b00f22055c3"><code>27c507b</code></a>
test: make sort-type-constituents tests fully static (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12262">#12262</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/a03b31d422be35d8ddc979f48f9317e3f33dd1ce"><code>a03b31d</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/a7099a789535b5c4cae0d6435ae9441e40da12b6"><code>a7099a7</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/bfbd4a5433deaa82c2e2a8eb746ab5cd97eaecf8"><code>bfbd4a5</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/b49d4b15cdd2e6fefcdaf7a50641b46c651231f2"><code>b49d4b1</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/3097e727474ef15a776071c07fb0752ab0e9d56d"><code>3097e72</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in nam...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/676191bbd7d0601492c536c88b24ca7cd3fe7d78"><code>676191b</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in mem...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/e9dce8bdc6a7460d10654b3ec834982f96b5ea8a"><code>e9dce8b</code></a>
fix(eslint-plugin): [no-unnecessary-condition] treat void as nullish in
no-un...</li>
<li>Additional commits viewable in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.58.0 to 8.59.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases"><code>@​typescript-eslint/parser</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v8.59.1</h2>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.59.0</h2>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.58.2</h2>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md"><code>@​typescript-eslint/parser</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>8.59.1 (2026-04-27)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.59.0 (2026-04-20)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.1 (2026-04-08)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/52457932e5507b5ca01e720a541f3f8d01e09b9d"><code>5245793</code></a>
chore(release): publish 8.59.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/ea9ae4f8817873480e3501145059f63e39e8d8a1"><code>ea9ae4f</code></a>
chore(release): publish 8.59.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/90c2803a4c250e0343598d41e973f95e743bf4ce"><code>90c2803</code></a>
chore(release): publish 8.58.2</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/be6b49a02d68db9bdc8985e7e9e1598700fda2fa"><code>be6b49a</code></a>
fix: remove tsbuildinfo cache file from published packages (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser/issues/12187">#12187</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/5311ed312eadf4e238324f2726ae0b1f3f2206e6"><code>5311ed3</code></a>
chore(release): publish 8.58.1</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `dotenv` from 17.3.1 to 17.4.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md">dotenv's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v17.4.1...v17.4.2">17.4.2</a>
(2026-04-12)</h2>
<h3>Changed</h3>
<ul>
<li>Improved skill files - tightened up details (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1009">#1009</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v17.4.0...v17.4.1">17.4.1</a>
(2026-04-05)</h2>
<h3>Changed</h3>
<ul>
<li>Change text <code>injecting</code> to <code>injected</code> (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1005">#1005</a>)</li>
</ul>
<h2><a
href="https://github.com/motdotla/dotenv/compare/v17.3.1...v17.4.0">17.4.0</a>
(2026-04-01)</h2>
<h3>Added</h3>
<ul>
<li>Add <code>skills/</code> folder with focused agent skills:
<code>skills/dotenv/SKILL.md</code> (core usage) and
<code>skills/dotenvx/SKILL.md</code> (encryption, multiple environments,
variable expansion) for AI coding agent discovery via the skills.sh
ecosystem (<code>npx skills add motdotla/dotenv</code>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Tighten up logs: <code>◇ injecting env (14) from .env</code> (<a
href="https://redirect.github.com/motdotla/dotenv/pull/1003">#1003</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/motdotla/dotenv/commit/f116f70310abab44fbfddbaeb833698b5bf84a9b"><code>f116f70</code></a>
17.4.2</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/3a8161274fdd745239b86e604f4a7e972a1d3902"><code>3a81612</code></a>
fix visual order of faq</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/13f55a89e136b2024e68d277b836dd5260fc16cf"><code>13f55a8</code></a>
Merge branch 'skill'</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/4bbbf73f0906bd69975c48bf310a84b686e5b1b4"><code>4bbbf73</code></a>
reorganize faq</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/c3da64bb2ba1d0e02f8b9b2b7ccb7e6f7a51d56c"><code>c3da64b</code></a>
Merge pull request <a
href="https://redirect.github.com/motdotla/dotenv/issues/1009">#1009</a>
from motdotla/skill</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/6f743b173fbd6c26f7eab7040d251f9a6c8b977d"><code>6f743b1</code></a>
update source</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/fc2c6247e858a32d4024cb06a5b0c79aa35851f5"><code>fc2c624</code></a>
update skill</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/972315ba74bb2bbba4483d112e853fd26006ef8a"><code>972315b</code></a>
Tighten up skill</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/2795fce3d1ed07b4c570f1e06ab1c0d533c86997"><code>2795fce</code></a>
reorganize faq</li>
<li><a
href="https://github.com/motdotla/dotenv/commit/d5495d4ae8e4e41ef9a682c9e00c81552794274e"><code>d5495d4</code></a>
adjust skill</li>
<li>Additional commits viewable in <a
href="https://github.com/motdotla/dotenv/compare/v17.3.1...v17.4.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.1.0 to 10.2.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.2.1</h2>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/14be92b6d1fa0923b8923830f2208e5e2705b002"><code>14be92b</code></a>
fix: model generator yield resumption paths in code path analysis (<a
href="https://redirect.github.com/eslint/eslint/issues/20665">#20665</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/84a19d2c32255db6b9cfc08644a607aae6d5cb62"><code>84a19d2</code></a>
fix: no-async-promise-executor false positives for shadowed Promise (<a
href="https://redirect.github.com/eslint/eslint/issues/20740">#20740</a>)
(xbinaryx)</li>
<li><a
href="https://github.com/eslint/eslint/commit/af764af0ec38225755fbf8a6f207f0c77b595a8d"><code>af764af</code></a>
fix: clarify language and processor validation errors (<a
href="https://redirect.github.com/eslint/eslint/issues/20729">#20729</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e251b89a38280973e468a4a9386c138f4f55d10d"><code>e251b89</code></a>
fix: update eslint (<a
href="https://redirect.github.com/eslint/eslint/issues/20715">#20715</a>)
(renovate[bot])</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/ca92ca0fb4599e8de1e2fb914e695fe7397cbe63"><code>ca92ca0</code></a>
docs: reuse markdown-it instance for markdown filter (<a
href="https://redirect.github.com/eslint/eslint/issues/20768">#20768</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/57d2ee213305cee0cb55ef08e0480b57396269a9"><code>57d2ee2</code></a>
docs: Enable Eleventy incremental mode for watch (<a
href="https://redirect.github.com/eslint/eslint/issues/20767">#20767</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c1621b915742276e5f4b25efe790ca62296330dc"><code>c1621b9</code></a>
docs: fix typos in code-path-analyzer.js (<a
href="https://redirect.github.com/eslint/eslint/issues/20700">#20700</a>)
(Ayush Shukla)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1418d522d10bde1960f4942afb548bc7160ec49e"><code>1418d52</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/39771e6e600f0b0617fdeafff6dd07e4211ffde6"><code>39771e6</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/71e04693def2df57268f08f3072a2749df6bf438"><code>71e0469</code></a>
docs: fix incomplete JSDoc param description in no-shadow rule (<a
href="https://redirect.github.com/eslint/eslint/issues/20728">#20728</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/22119ceb93e28f62262fc1d98ff1b1442d6e2dbf"><code>22119ce</code></a>
docs: clarify scope of for-direction rule with dead code examples (<a
href="https://redirect.github.com/eslint/eslint/issues/20723">#20723</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8f3fb77f122a5641d1833cad5d93f3f54fa3be0b"><code>8f3fb77</code></a>
docs: document <code>meta.docs.dialects</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20718">#20718</a>)
(Pixel998)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/7ddfea9c4f62add1588c5c0b0da568c299246383"><code>7ddfea9</code></a>
chore: update dependency prettier to v3.8.2 (<a
href="https://redirect.github.com/eslint/eslint/issues/20770">#20770</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/fac40e1de2ba7646cc7cd2d3f93fbdd1f8819001"><code>fac40e1</code></a>
ci: bump pnpm/action-setup from 5.0.0 to 6.0.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20763">#20763</a>)
(dependabot[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/7246f923332522d8b3d46b6ee646fce88535f3fb"><code>7246f92</code></a>
test: add tests for SuppressionsService.load() error handling (<a
href="https://redirect.github.com/eslint/eslint/issues/20734">#20734</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4f34b1e592b0f63d766d9903998e8e36eb49d3aa"><code>4f34b1e</code></a>
chore: update pnpm/action-setup action to v5 (<a
href="https://redirect.github.com/eslint/eslint/issues/20762">#20762</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/51080eb5c98d619434e4835dbe9f1c6654aca3b8"><code>51080eb</code></a>
test: processor service (<a
href="https://redirect.github.com/eslint/eslint/issues/20731">#20731</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e7e1889fca9b6044e08f41b38df20a1ce45808c8"><code>e7e1889</code></a>
chore: remove stale babel-eslint10 fixture and test (<a
href="https://redirect.github.com/eslint/eslint/issues/20727">#20727</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4e1a87cb8fb90e309524bc36bc5f31b9f9cfaa76"><code>4e1a87c</code></a>
test: remove redundant async/await in flat config array tests (<a
href="https://redirect.github.com/eslint/eslint/issues/20722">#20722</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/066eabb3643b12931f991594969bcc0028f71a5f"><code>066eabb</code></a>
test: add rule metadata coverage for <code>languages</code> and
<code>docs.dialects</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20717">#20717</a>)
(Pixel998)</li>
</ul>
<h2>v10.2.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/586ec2f43092779acc957866db4abe999112d1e1"><code>586ec2f</code></a>
feat: Add <code>meta.languages</code> support to rules (<a
href="https://redirect.github.com/eslint/eslint/issues/20571">#20571</a>)
(Copilot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/14207dee3939dc87cfa8b2fcfc271fff2cfd6471"><code>14207de</code></a>
feat: add <code>Temporal</code> to <code>no-obj-calls</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20675">#20675</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/bbb2c93a2b31bd30924f32fe69a9acf41f9dfe35"><code>bbb2c93</code></a>
feat: add Temporal to ES2026 globals (<a
href="https://redirect.github.com/eslint/eslint/issues/20672">#20672</a>)
(Pixel998)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/542cb3e6442a4e6ee3457c799e2a0ee23bef0c6a"><code>542cb3e</code></a>
fix: update first-party dependencies (<a
href="https://redirect.github.com/eslint/eslint/issues/20714">#20714</a>)
(Francesco Trotta)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/a2af743ea60f683d0e0de9d98267c1e7e4f5e412"><code>a2af743</code></a>
docs: add <code>language</code> to configuration objects (<a
href="https://redirect.github.com/eslint/eslint/issues/20712">#20712</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/845f23f1370892bf07d819497ac518c9e65090d6"><code>845f23f</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5fbcf5958b897cc4df5d652924d18428db37f7ee"><code>5fbcf59</code></a>
docs: remove <code>sourceType</code> from ts playground link (<a
href="https://redirect.github.com/eslint/eslint/issues/20477">#20477</a>)
(Tanuj Kanti)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8702a474659be786b6b1392e5e7c0c56355ae4a4"><code>8702a47</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/ddeaded2ab36951383ff67c60fb64ec68d29a46a"><code>ddeaded</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/2b4496691266547784a7f7ad1989ce53381bab91"><code>2b44966</code></a>
docs: add Major Releases section to Manage Releases (<a
href="https://redirect.github.com/eslint/eslint/issues/20269">#20269</a>)
(Milos Djermanovic)</li>
<li><a
href="https://github.com/eslint/eslint/commit/eab65c700ebb16a6e790910c720450c9908961fd"><code>eab65c7</code></a>
docs: update <code>eslint</code> versions in examples (<a
href="https://redirect.github.com/eslint/eslint/issues/20664">#20664</a>)
(루밀LuMir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/3e4a29903bf31f0998e45ad9128a265bce1edc56"><code>3e4a299</code></a>
docs: update ESM Dependencies policies with note for own-usage packages
(<a
href="https://redirect.github.com/eslint/eslint/issues/20660">#20660</a>)
(Milos Djermanovic)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/8120e30f833474f47acc061d24d164e9f022264f"><code>8120e30</code></a>
refactor: extract no unmodified loop condition (<a
href="https://redirect.github.com/eslint/eslint/issues/20679">#20679</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/46e8469786be1b2bbb522100e1d44624d98d3745"><code>46e8469</code></a>
chore: update dependency markdownlint-cli2 to ^0.22.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20697">#20697</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/01ed3aa68477f81a7188e1498cf4906e02015b7c"><code>01ed3aa</code></a>
test: add unit tests for unicode utilities (<a
href="https://redirect.github.com/eslint/eslint/issues/20622">#20622</a>)
(Manish chaudhary)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/4d1d8f9737236603f64bbe83d5bb8001627b5611"><code>4d1d8f9</code></a>
10.2.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/3e33105b05d09b5a4eb894ed75a9811fb40d65e6"><code>3e33105</code></a>
Build: changelog update for 10.2.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/ca92ca0fb4599e8de1e2fb914e695fe7397cbe63"><code>ca92ca0</code></a>
docs: reuse markdown-it instance for markdown filter (<a
href="https://redirect.github.com/eslint/eslint/issues/20768">#20768</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/7ddfea9c4f62add1588c5c0b0da568c299246383"><code>7ddfea9</code></a>
chore: update dependency prettier to v3.8.2 (<a
href="https://redirect.github.com/eslint/eslint/issues/20770">#20770</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/57d2ee213305cee0cb55ef08e0480b57396269a9"><code>57d2ee2</code></a>
docs: Enable Eleventy incremental mode for watch (<a
href="https://redirect.github.com/eslint/eslint/issues/20767">#20767</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c1621b915742276e5f4b25efe790ca62296330dc"><code>c1621b9</code></a>
docs: fix typos in code-path-analyzer.js (<a
href="https://redirect.github.com/eslint/eslint/issues/20700">#20700</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/fac40e1de2ba7646cc7cd2d3f93fbdd1f8819001"><code>fac40e1</code></a>
ci: bump pnpm/action-setup from 5.0.0 to 6.0.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20763">#20763</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/7246f923332522d8b3d46b6ee646fce88535f3fb"><code>7246f92</code></a>
test: add tests for SuppressionsService.load() error handling (<a
href="https://redirect.github.com/eslint/eslint/issues/20734">#20734</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4f34b1e592b0f63d766d9903998e8e36eb49d3aa"><code>4f34b1e</code></a>
chore: update pnpm/action-setup action to v5 (<a
href="https://redirect.github.com/eslint/eslint/issues/20762">#20762</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1418d522d10bde1960f4942afb548bc7160ec49e"><code>1418d52</code></a>
docs: Update README</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.1.0...v10.2.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `prettier` from 3.8.1 to 3.8.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/releases">prettier's
releases</a>.</em></p>
<blockquote>
<h2>3.8.3</h2>
<ul>
<li>SCSS: Prevent trailing comma in <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/pull/18471">prettier/prettier#18471</a>
by <a href="https://github.com/kovsu"><code>@​kovsu</code></a>)</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/3.8.3/CHANGELOG.md#383">Changelog</a></p>
<h2>3.8.2</h2>
<ul>
<li>Support Angular v21.2</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#382">Changelog</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's
changelog</a>.</em></p>
<blockquote>
<h1>3.8.3</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.8.2...3.8.3">diff</a></p>
<h4>SCSS: Prevent trailing comma in <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/pull/18471">#18471</a>
by <a href="https://github.com/kovsu"><code>@​kovsu</code></a>)</h4>
<!-- raw HTML omitted -->
<pre lang="scss"><code>// Input
$value: if(sass(false): 1; else: -1);
<p>// Prettier 3.8.2
$value: if(
sass(false): 1; else: -1,
);</p>
<p>// Prettier 3.8.3
$value: if(sass(false): 1; else: -1);
</code></pre></p>
<h1>3.8.2</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.8.1...3.8.2">diff</a></p>
<h4>Angular: Support Angular v21.2 (<a
href="https://redirect.github.com/prettier/prettier/pull/18722">#18722</a>,
<a
href="https://redirect.github.com/prettier/prettier/pull/19034">#19034</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<p>Exhaustive typechecking with <code>@default never;</code></p>
<!-- raw HTML omitted -->
<pre lang="html"><code>&lt;!-- Input --&gt;
@switch (foo) {
  @case (1) {}
  @default never;
}
<p>&lt;!-- Prettier 3.8.1 --&gt;
SyntaxError: Incomplete block &quot;default never&quot;. If you meant to
write the @ character, you should use the &quot;&amp;<a
href="https://redirect.github.com/prettier/prettier/issues/64">#64</a>;&quot;
HTML entity instead. (3:3)</p>
<p>&lt;!-- Prettier 3.8.2 --&gt;
<a href="https://github.com/switch"><code>@​switch</code></a> (foo) {
<a href="https://github.com/case"><code>@​case</code></a> (1) {}
<a href="https://github.com/default"><code>@​default</code></a> never;
}
</code></pre></p>
<p><code>arrow function</code> and <code>instanceof</code>
expressions.</p>
<!-- raw HTML omitted -->
<pre lang="html"><code>&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/prettier/prettier/commit/d7108a79ec745c04292aabf22c4c1adbd690b191"><code>d7108a7</code></a>
Release 3.8.3</li>
<li><a
href="https://github.com/prettier/prettier/commit/177f90898170d363ef64fde663e4d13170688bfe"><code>177f908</code></a>
Prevent trailing comma in SCSS <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/issues/18471">#18471</a>)</li>
<li><a
href="https://github.com/prettier/prettier/commit/1cd40668c3d6f2f4cf9d87bbc9096d92361b2606"><code>1cd4066</code></a>
Release <code>@​prettier/plugin-oxc</code><a
href="https://github.com/0"><code>@​0</code></a>.1.4</li>
<li><a
href="https://github.com/prettier/prettier/commit/a8700e245038cd8cc0cf28ef06ffedbcb3fc2dfc"><code>a8700e2</code></a>
Update oxc-parser to v0.125.0</li>
<li><a
href="https://github.com/prettier/prettier/commit/752157c78eca6f0a30e5d5cb513b682c5ecfa01e"><code>752157c</code></a>
Fix tests</li>
<li><a
href="https://github.com/prettier/prettier/commit/053fd418e180b12fa2014260212fae831f5fc5ec"><code>053fd41</code></a>
Bump Prettier dependency to 3.8.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/904c6365ec46726fd0e21021c52ae934b7e5abc6"><code>904c636</code></a>
Clean changelog_unreleased</li>
<li><a
href="https://github.com/prettier/prettier/commit/dc1f7fcc508d116cbf1644d69a1f0eb93e40d4a4"><code>dc1f7fc</code></a>
Update dependents count</li>
<li><a
href="https://github.com/prettier/prettier/commit/b31557cf331a02acf83e7e29d1001b070189a0d9"><code>b31557c</code></a>
Release 3.8.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/96bbaeda0525bf758e464aed2f939d739a85c315"><code>96bbaed</code></a>
Support Angular v21.2 (<a
href="https://redirect.github.com/prettier/prettier/issues/18722">#18722</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/prettier/prettier/compare/3.8.1...3.8.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ts-jest` from 29.4.6 to 29.4.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/kulshekhar/ts-jest/releases">ts-jest's
releases</a>.</em></p>
<blockquote>
<h2>v29.4.9</h2>
<p>Please refer to <a
href="https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v29.4.8</h2>
<p>No release notes provided.</p>
<h2>v29.4.7</h2>
<p>Please refer to <a
href="https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md">ts-jest's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/kulshekhar/ts-jest/compare/v29.4.6...v29.4.7">29.4.7</a>
(2026-04-01)</h2>
<h3>Features</h3>
<ul>
<li>support TypeScript v6 (<a
href="https://github.com/kulshekhar/ts-jest/commit/eda517d226389317d99572887d3c1aa93c81be87">eda517d</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/bac2e77231418c35cb9b9946d7d871c0ee2322a1"><code>bac2e77</code></a>
chore(release): bump version to 29.4.9</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/f8a9cc98924bd1846aac96d6c544e6626f031f81"><code>f8a9cc9</code></a>
fix: use correct registry for npm OIDC trusted publishing</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/e2eec26aa127e284719b4754937d17bd57b13518"><code>e2eec26</code></a>
fix: npm permissions</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/263f2ac73e4a0c4163f8766c3ba9d0a13ca78812"><code>263f2ac</code></a>
chore: remove npm auth token</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/5df0e45f409994b34cae7d22db60946a8158d975"><code>5df0e45</code></a>
OIDC</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/f82c1444d4af0d9f27883510acc8178c18ee8f8a"><code>f82c144</code></a>
Merge pull request <a
href="https://redirect.github.com/kulshekhar/ts-jest/issues/5250">#5250</a>
from kulshekhar/copilot/bump-patch-version</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/e6ec5aed410a803c2f972b3287cf233ce4c998f9"><code>e6ec5ae</code></a>
Update CHANGELOG.md</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/62c3199a355d3ba483f0e976c868ddc6f20d53fe"><code>62c3199</code></a>
Update CHANGELOG.md</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/052e751f124d059e1363b827f1709ed7607daded"><code>052e751</code></a>
Bump patch version to 29.4.7</li>
<li><a
href="https://github.com/kulshekhar/ts-jest/commit/f79e77b64f4a807605e277b1ea05f10abe9c94f0"><code>f79e77b</code></a>
Merge pull request <a
href="https://redirect.github.com/kulshekhar/ts-jest/issues/5249">#5249</a>
from ext/feature/ts6-peer</li>
<li>Additional commits viewable in <a
href="https://github.com/kulshekhar/ts-jest/compare/v29.4.6...v29.4.9">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for ts-jest since your current version.</p>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:13:48 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4e833ceabe chore(deps): bump the minor-and-patch group in /libs/cli/js-monorepo-example with 7 updates (#7671)
Bumps the minor-and-patch group in /libs/cli/js-monorepo-example with 7
updates:

| Package | From | To |
| --- | --- | --- |
| [turbo](https://github.com/vercel/turborepo) | `2.9.3` | `2.9.7` |
| [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.2.1` |
|
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
| `8.58.0` | `8.59.1` |
|
[@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)
| `8.58.0` | `8.59.1` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.3` |
| [@langchain/core](https://github.com/langchain-ai/langchainjs) |
`1.1.38` | `1.1.42` |
|
[@langchain/langgraph](https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core)
| `1.2.6` | `1.2.9` |

Updates `turbo` from 2.9.3 to 2.9.7
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vercel/turborepo/releases">turbo's
releases</a>.</em></p>
<blockquote>
<h2>Turborepo v2.9.7</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>eslint</h3>
<ul>
<li>chore: Upgrade dependencies to resolve their known vulnerabilities
by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12604">vercel/turborepo#12604</a></li>
</ul>
<h3>Examples</h3>
<ul>
<li>feat(sandbox): Bump <code>@​vercel/sandbox</code> from v1 to beta by
<a href="https://github.com/marc-vercel"><code>@​marc-vercel</code></a>
in <a
href="https://redirect.github.com/vercel/turborepo/pull/12595">vercel/turborepo#12595</a></li>
<li>chore: Update examples to Turbo 2.9.6 by <a
href="https://github.com/cursor"><code>@​cursor</code></a>[bot] in <a
href="https://redirect.github.com/vercel/turborepo/pull/12600">vercel/turborepo#12600</a></li>
<li>examples: Add Ultracite example by <a
href="https://github.com/haydenbleasel"><code>@​haydenbleasel</code></a>
in <a
href="https://redirect.github.com/vercel/turborepo/pull/12615">vercel/turborepo#12615</a></li>
</ul>
<h3>Changelog</h3>
<ul>
<li>fix: Align Markdown docs routing with docs/md endpoints by <a
href="https://github.com/molebox"><code>@​molebox</code></a> in <a
href="https://redirect.github.com/vercel/turborepo/pull/12596">vercel/turborepo#12596</a></li>
<li>fix: Support two-dot git ranges in filter selectors by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12599">vercel/turborepo#12599</a></li>
<li>feat: Graceful shutdown by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12607">vercel/turborepo#12607</a></li>
<li>test: Add stdin EOF startup regression coverage by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12609">vercel/turborepo#12609</a></li>
<li>fix: Ignore <code>SIGINT</code> in shim after spawning local
<code>turbo</code> by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12612">vercel/turborepo#12612</a></li>
<li>fix: Support pnpm v11 multi-document lockfiles by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12616">vercel/turborepo#12616</a></li>
<li>fix: Preserve graceful shutdown exit code by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12620">vercel/turborepo#12620</a></li>
<li>fix: Keep Node wrapper alive during graceful shutdown by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12622">vercel/turborepo#12622</a></li>
<li>fix: Preserve PTY graceful shutdown semantics by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12624">vercel/turborepo#12624</a></li>
<li>feat: Move Vercel auth to standard OAuth/device flows by <a
href="https://github.com/markandrus"><code>@​markandrus</code></a> in <a
href="https://redirect.github.com/vercel/turborepo/pull/12526">vercel/turborepo#12526</a></li>
<li>fix: Preserve legacy Vercel auth compatibility by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12629">vercel/turborepo#12629</a></li>
<li>fix: Recover Vercel auth tokens across login flows by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12631">vercel/turborepo#12631</a></li>
<li>docs: Fix TURBO_PLATFORM_ENV_DISABLED value in docs (true, not
false) by <a
href="https://github.com/sleitor"><code>@​sleitor</code></a> in <a
href="https://redirect.github.com/vercel/turborepo/pull/12633">vercel/turborepo#12633</a></li>
<li>chore: Update flags SDK by <a
href="https://github.com/AndyBitz"><code>@​AndyBitz</code></a> in <a
href="https://redirect.github.com/vercel/turborepo/pull/12646">vercel/turborepo#12646</a></li>
<li>ci: Disable AWS-backed sccache by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12663">vercel/turborepo#12663</a></li>
<li>fix: Prevent prune from overmatching gitignore entries by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12662">vercel/turborepo#12662</a></li>
<li>ci: Harden release API commits by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12664">vercel/turborepo#12664</a></li>
<li>ci: Fix release API commit paths by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12665">vercel/turborepo#12665</a></li>
<li>release(turborepo): 2.9.7-canary.14 by <a
href="https://github.com/github-actions"><code>@​github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/vercel/turborepo/pull/12666">vercel/turborepo#12666</a></li>
<li>chore: Add <code>tbx</code> sandbox helper by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12668">vercel/turborepo#12668</a></li>
<li>fix: Allow npm registry in tbx sandboxes by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12669">vercel/turborepo#12669</a></li>
<li>fix: Install turbo globally in tbx base by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12670">vercel/turborepo#12670</a></li>
<li>docs: Clarify package hash file inputs by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12671">vercel/turborepo#12671</a></li>
<li>fix: Allow tbx sandboxes to use stale bases by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12672">vercel/turborepo#12672</a></li>
<li>fix: Install dotfiles during <code>tbx base refresh</code> by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12673">vercel/turborepo#12673</a></li>
<li>fix: Improve tbx sandbox startup by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12674">vercel/turborepo#12674</a></li>
<li>fix: Improve tbx sandbox startup defaults by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12675">vercel/turborepo#12675</a></li>
<li>fix: Support pnpm 11 flat patch lockfiles by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12676">vercel/turborepo#12676</a></li>
<li>docs: Fix link to passthrough variables source code by <a
href="https://github.com/Wartijn"><code>@​Wartijn</code></a> in <a
href="https://redirect.github.com/vercel/turborepo/pull/12643">vercel/turborepo#12643</a></li>
<li>release(turborepo): 2.9.7-canary.15 by <a
href="https://github.com/github-actions"><code>@​github-actions</code></a>[bot]
in <a
href="https://redirect.github.com/vercel/turborepo/pull/12677">vercel/turborepo#12677</a></li>
<li>fix: Avoid rerunning non-cacheable watch dependencies by <a
href="https://github.com/anthonyshew"><code>@​anthonyshew</code></a> in
<a
href="https://redirect.github.com/vercel/turborepo/pull/12678">vercel/turborepo#12678</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/marc-vercel"><code>@​marc-vercel</code></a>
made their first contribution in <a
href="https://redirect.github.com/vercel/turborepo/pull/12595">vercel/turborepo#12595</a></li>
<li><a href="https://github.com/cursor"><code>@​cursor</code></a>[bot]
made their first contribution in <a
href="https://redirect.github.com/vercel/turborepo/pull/12600">vercel/turborepo#12600</a></li>
<li><a
href="https://github.com/markandrus"><code>@​markandrus</code></a> made
their first contribution in <a
href="https://redirect.github.com/vercel/turborepo/pull/12526">vercel/turborepo#12526</a></li>
<li><a href="https://github.com/AndyBitz"><code>@​AndyBitz</code></a>
made their first contribution in <a
href="https://redirect.github.com/vercel/turborepo/pull/12646">vercel/turborepo#12646</a></li>
<li><a href="https://github.com/Wartijn"><code>@​Wartijn</code></a> made
their first contribution in <a
href="https://redirect.github.com/vercel/turborepo/pull/12643">vercel/turborepo#12643</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vercel/turborepo/commit/93d74d0a6a06eeb6d7561077e9a7a68f69b8261a"><code>93d74d0</code></a>
publish 2.9.7 to registry</li>
<li><a
href="https://github.com/vercel/turborepo/commit/94775710c0eb6a0511365b49eb2c79d2265d863a"><code>9477571</code></a>
fix: Avoid rerunning non-cacheable watch dependencies (<a
href="https://redirect.github.com/vercel/turborepo/issues/12678">#12678</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/bca27f077a73a5b293c3bda312e0ef140bd952bc"><code>bca27f0</code></a>
release(turborepo): 2.9.7-canary.15 (<a
href="https://redirect.github.com/vercel/turborepo/issues/12677">#12677</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/7c64d46c9aea1066abb077310583697855533988"><code>7c64d46</code></a>
docs: Fix link to passthrough variables source code (<a
href="https://redirect.github.com/vercel/turborepo/issues/12643">#12643</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/976bdce05ea4512734522ff1e8d7ec78d9788b8a"><code>976bdce</code></a>
fix: Support pnpm 11 flat patch lockfiles (<a
href="https://redirect.github.com/vercel/turborepo/issues/12676">#12676</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/409b6d82f5a20543af2be5c798a1a019ffd8d9f5"><code>409b6d8</code></a>
fix: Improve tbx sandbox startup defaults (<a
href="https://redirect.github.com/vercel/turborepo/issues/12675">#12675</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/aab6e38816713b92af5b7bd69462c504ad009f7d"><code>aab6e38</code></a>
fix: Improve tbx sandbox startup (<a
href="https://redirect.github.com/vercel/turborepo/issues/12674">#12674</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/7a3023dc96f80ea9479d285d739f8691e19b63ec"><code>7a3023d</code></a>
fix: Install dotfiles during <code>tbx base refresh</code> (<a
href="https://redirect.github.com/vercel/turborepo/issues/12673">#12673</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/ce65df51ced5272f30b68157b5e3e5cb275a9c06"><code>ce65df5</code></a>
fix: Allow tbx sandboxes to use stale bases (<a
href="https://redirect.github.com/vercel/turborepo/issues/12672">#12672</a>)</li>
<li><a
href="https://github.com/vercel/turborepo/commit/fdfed0368c448af6e0e72c9c97a9ff688b17632f"><code>fdfed03</code></a>
docs: Clarify package hash file inputs (<a
href="https://redirect.github.com/vercel/turborepo/issues/12671">#12671</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vercel/turborepo/compare/v2.9.3...v2.9.7">compare
view</a></li>
</ul>
</details>
<br />

Updates `eslint` from 10.1.0 to 10.2.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.2.1</h2>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/14be92b6d1fa0923b8923830f2208e5e2705b002"><code>14be92b</code></a>
fix: model generator yield resumption paths in code path analysis (<a
href="https://redirect.github.com/eslint/eslint/issues/20665">#20665</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/84a19d2c32255db6b9cfc08644a607aae6d5cb62"><code>84a19d2</code></a>
fix: no-async-promise-executor false positives for shadowed Promise (<a
href="https://redirect.github.com/eslint/eslint/issues/20740">#20740</a>)
(xbinaryx)</li>
<li><a
href="https://github.com/eslint/eslint/commit/af764af0ec38225755fbf8a6f207f0c77b595a8d"><code>af764af</code></a>
fix: clarify language and processor validation errors (<a
href="https://redirect.github.com/eslint/eslint/issues/20729">#20729</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e251b89a38280973e468a4a9386c138f4f55d10d"><code>e251b89</code></a>
fix: update eslint (<a
href="https://redirect.github.com/eslint/eslint/issues/20715">#20715</a>)
(renovate[bot])</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/ca92ca0fb4599e8de1e2fb914e695fe7397cbe63"><code>ca92ca0</code></a>
docs: reuse markdown-it instance for markdown filter (<a
href="https://redirect.github.com/eslint/eslint/issues/20768">#20768</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/57d2ee213305cee0cb55ef08e0480b57396269a9"><code>57d2ee2</code></a>
docs: Enable Eleventy incremental mode for watch (<a
href="https://redirect.github.com/eslint/eslint/issues/20767">#20767</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c1621b915742276e5f4b25efe790ca62296330dc"><code>c1621b9</code></a>
docs: fix typos in code-path-analyzer.js (<a
href="https://redirect.github.com/eslint/eslint/issues/20700">#20700</a>)
(Ayush Shukla)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1418d522d10bde1960f4942afb548bc7160ec49e"><code>1418d52</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/39771e6e600f0b0617fdeafff6dd07e4211ffde6"><code>39771e6</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/71e04693def2df57268f08f3072a2749df6bf438"><code>71e0469</code></a>
docs: fix incomplete JSDoc param description in no-shadow rule (<a
href="https://redirect.github.com/eslint/eslint/issues/20728">#20728</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/22119ceb93e28f62262fc1d98ff1b1442d6e2dbf"><code>22119ce</code></a>
docs: clarify scope of for-direction rule with dead code examples (<a
href="https://redirect.github.com/eslint/eslint/issues/20723">#20723</a>)
(Amaresh S M)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8f3fb77f122a5641d1833cad5d93f3f54fa3be0b"><code>8f3fb77</code></a>
docs: document <code>meta.docs.dialects</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20718">#20718</a>)
(Pixel998)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/7ddfea9c4f62add1588c5c0b0da568c299246383"><code>7ddfea9</code></a>
chore: update dependency prettier to v3.8.2 (<a
href="https://redirect.github.com/eslint/eslint/issues/20770">#20770</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/fac40e1de2ba7646cc7cd2d3f93fbdd1f8819001"><code>fac40e1</code></a>
ci: bump pnpm/action-setup from 5.0.0 to 6.0.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20763">#20763</a>)
(dependabot[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/7246f923332522d8b3d46b6ee646fce88535f3fb"><code>7246f92</code></a>
test: add tests for SuppressionsService.load() error handling (<a
href="https://redirect.github.com/eslint/eslint/issues/20734">#20734</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4f34b1e592b0f63d766d9903998e8e36eb49d3aa"><code>4f34b1e</code></a>
chore: update pnpm/action-setup action to v5 (<a
href="https://redirect.github.com/eslint/eslint/issues/20762">#20762</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/51080eb5c98d619434e4835dbe9f1c6654aca3b8"><code>51080eb</code></a>
test: processor service (<a
href="https://redirect.github.com/eslint/eslint/issues/20731">#20731</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e7e1889fca9b6044e08f41b38df20a1ce45808c8"><code>e7e1889</code></a>
chore: remove stale babel-eslint10 fixture and test (<a
href="https://redirect.github.com/eslint/eslint/issues/20727">#20727</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4e1a87cb8fb90e309524bc36bc5f31b9f9cfaa76"><code>4e1a87c</code></a>
test: remove redundant async/await in flat config array tests (<a
href="https://redirect.github.com/eslint/eslint/issues/20722">#20722</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/066eabb3643b12931f991594969bcc0028f71a5f"><code>066eabb</code></a>
test: add rule metadata coverage for <code>languages</code> and
<code>docs.dialects</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20717">#20717</a>)
(Pixel998)</li>
</ul>
<h2>v10.2.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/586ec2f43092779acc957866db4abe999112d1e1"><code>586ec2f</code></a>
feat: Add <code>meta.languages</code> support to rules (<a
href="https://redirect.github.com/eslint/eslint/issues/20571">#20571</a>)
(Copilot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/14207dee3939dc87cfa8b2fcfc271fff2cfd6471"><code>14207de</code></a>
feat: add <code>Temporal</code> to <code>no-obj-calls</code> (<a
href="https://redirect.github.com/eslint/eslint/issues/20675">#20675</a>)
(Pixel998)</li>
<li><a
href="https://github.com/eslint/eslint/commit/bbb2c93a2b31bd30924f32fe69a9acf41f9dfe35"><code>bbb2c93</code></a>
feat: add Temporal to ES2026 globals (<a
href="https://redirect.github.com/eslint/eslint/issues/20672">#20672</a>)
(Pixel998)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/542cb3e6442a4e6ee3457c799e2a0ee23bef0c6a"><code>542cb3e</code></a>
fix: update first-party dependencies (<a
href="https://redirect.github.com/eslint/eslint/issues/20714">#20714</a>)
(Francesco Trotta)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/a2af743ea60f683d0e0de9d98267c1e7e4f5e412"><code>a2af743</code></a>
docs: add <code>language</code> to configuration objects (<a
href="https://redirect.github.com/eslint/eslint/issues/20712">#20712</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/845f23f1370892bf07d819497ac518c9e65090d6"><code>845f23f</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5fbcf5958b897cc4df5d652924d18428db37f7ee"><code>5fbcf59</code></a>
docs: remove <code>sourceType</code> from ts playground link (<a
href="https://redirect.github.com/eslint/eslint/issues/20477">#20477</a>)
(Tanuj Kanti)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8702a474659be786b6b1392e5e7c0c56355ae4a4"><code>8702a47</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/ddeaded2ab36951383ff67c60fb64ec68d29a46a"><code>ddeaded</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/2b4496691266547784a7f7ad1989ce53381bab91"><code>2b44966</code></a>
docs: add Major Releases section to Manage Releases (<a
href="https://redirect.github.com/eslint/eslint/issues/20269">#20269</a>)
(Milos Djermanovic)</li>
<li><a
href="https://github.com/eslint/eslint/commit/eab65c700ebb16a6e790910c720450c9908961fd"><code>eab65c7</code></a>
docs: update <code>eslint</code> versions in examples (<a
href="https://redirect.github.com/eslint/eslint/issues/20664">#20664</a>)
(루밀LuMir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/3e4a29903bf31f0998e45ad9128a265bce1edc56"><code>3e4a299</code></a>
docs: update ESM Dependencies policies with note for own-usage packages
(<a
href="https://redirect.github.com/eslint/eslint/issues/20660">#20660</a>)
(Milos Djermanovic)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/8120e30f833474f47acc061d24d164e9f022264f"><code>8120e30</code></a>
refactor: extract no unmodified loop condition (<a
href="https://redirect.github.com/eslint/eslint/issues/20679">#20679</a>)
(kuldeep kumar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/46e8469786be1b2bbb522100e1d44624d98d3745"><code>46e8469</code></a>
chore: update dependency markdownlint-cli2 to ^0.22.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20697">#20697</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/01ed3aa68477f81a7188e1498cf4906e02015b7c"><code>01ed3aa</code></a>
test: add unit tests for unicode utilities (<a
href="https://redirect.github.com/eslint/eslint/issues/20622">#20622</a>)
(Manish chaudhary)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/4d1d8f9737236603f64bbe83d5bb8001627b5611"><code>4d1d8f9</code></a>
10.2.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/3e33105b05d09b5a4eb894ed75a9811fb40d65e6"><code>3e33105</code></a>
Build: changelog update for 10.2.1</li>
<li><a
href="https://github.com/eslint/eslint/commit/ca92ca0fb4599e8de1e2fb914e695fe7397cbe63"><code>ca92ca0</code></a>
docs: reuse markdown-it instance for markdown filter (<a
href="https://redirect.github.com/eslint/eslint/issues/20768">#20768</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/7ddfea9c4f62add1588c5c0b0da568c299246383"><code>7ddfea9</code></a>
chore: update dependency prettier to v3.8.2 (<a
href="https://redirect.github.com/eslint/eslint/issues/20770">#20770</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/57d2ee213305cee0cb55ef08e0480b57396269a9"><code>57d2ee2</code></a>
docs: Enable Eleventy incremental mode for watch (<a
href="https://redirect.github.com/eslint/eslint/issues/20767">#20767</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c1621b915742276e5f4b25efe790ca62296330dc"><code>c1621b9</code></a>
docs: fix typos in code-path-analyzer.js (<a
href="https://redirect.github.com/eslint/eslint/issues/20700">#20700</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/fac40e1de2ba7646cc7cd2d3f93fbdd1f8819001"><code>fac40e1</code></a>
ci: bump pnpm/action-setup from 5.0.0 to 6.0.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/20763">#20763</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/7246f923332522d8b3d46b6ee646fce88535f3fb"><code>7246f92</code></a>
test: add tests for SuppressionsService.load() error handling (<a
href="https://redirect.github.com/eslint/eslint/issues/20734">#20734</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/4f34b1e592b0f63d766d9903998e8e36eb49d3aa"><code>4f34b1e</code></a>
chore: update pnpm/action-setup action to v5 (<a
href="https://redirect.github.com/eslint/eslint/issues/20762">#20762</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1418d522d10bde1960f4942afb548bc7160ec49e"><code>1418d52</code></a>
docs: Update README</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.1.0...v10.2.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/eslint-plugin` from 8.58.0 to 8.59.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases"><code>@​typescript-eslint/eslint-plugin</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v8.59.1</h2>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.59.0</h2>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.58.2</h2>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md"><code>@​typescript-eslint/eslint-plugin</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2">GitHub
Releases</a> for more information.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/52457932e5507b5ca01e720a541f3f8d01e09b9d"><code>5245793</code></a>
chore(release): publish 8.59.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/3cef12471659c42c9d4b179f4113a080b1fa2d66"><code>3cef124</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in dot...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/27c507b6e532abcd1efa51055f045b00f22055c3"><code>27c507b</code></a>
test: make sort-type-constituents tests fully static (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12262">#12262</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/a03b31d422be35d8ddc979f48f9317e3f33dd1ce"><code>a03b31d</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/a7099a789535b5c4cae0d6435ae9441e40da12b6"><code>a7099a7</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/bfbd4a5433deaa82c2e2a8eb746ab5cd97eaecf8"><code>bfbd4a5</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/b49d4b15cdd2e6fefcdaf7a50641b46c651231f2"><code>b49d4b1</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in no-...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/3097e727474ef15a776071c07fb0752ab0e9d56d"><code>3097e72</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in nam...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/676191bbd7d0601492c536c88b24ca7cd3fe7d78"><code>676191b</code></a>
chore(eslint-plugin): switch auto-generated test cases to hand-written
in mem...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/e9dce8bdc6a7460d10654b3ec834982f96b5ea8a"><code>e9dce8b</code></a>
fix(eslint-plugin): [no-unnecessary-condition] treat void as nullish in
no-un...</li>
<li>Additional commits viewable in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/parser` from 8.58.0 to 8.59.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases"><code>@​typescript-eslint/parser</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v8.59.1</h2>
<h2>8.59.1 (2026-04-27)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion] fix
crash &quot;TypeError: checker.getTypeArguments is not a function&quot;
(<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12246">#12246</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve index signatures in undefined unions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12257">#12257</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
preserve phantom type arguments in generic inference (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12269">#12269</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
avoid false positive in logical assignment assertions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12278">#12278</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-arguments]
handle instantiation expressions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12220">#12220</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] treat
void as nullish in no-unnecessary-condition (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12241">#12241</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>anasm266 <a
href="https://github.com/anasm266"><code>@​anasm266</code></a></li>
<li>Anshika Jain <a
href="https://github.com/Anshikakalpana"><code>@​Anshikakalpana</code></a></li>
<li>Ulrich Stark</li>
<li>yugo innami <a
href="https://github.com/nami8824"><code>@​nami8824</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.59.0</h2>
<h2>8.59.0 (2026-04-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
report more cases based on assignability (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11789">#11789</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Ulrich Stark</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.58.2</h2>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-condition] use
assignability checks in checkTypePredicates (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12147">#12147</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
<li>송재욱</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md"><code>@​typescript-eslint/parser</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>8.59.1 (2026-04-27)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.59.0 (2026-04-20)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.59.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.2 (2026-04-13)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li>remove tsbuildinfo cache file from published packages (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12187">#12187</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Abhijeet Singh <a
href="https://github.com/cseas"><code>@​cseas</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.2">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.58.1 (2026-04-08)</h2>
<p>This was a version bump only for parser to align it with other
projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.58.1">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/52457932e5507b5ca01e720a541f3f8d01e09b9d"><code>5245793</code></a>
chore(release): publish 8.59.1</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/ea9ae4f8817873480e3501145059f63e39e8d8a1"><code>ea9ae4f</code></a>
chore(release): publish 8.59.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/90c2803a4c250e0343598d41e973f95e743bf4ce"><code>90c2803</code></a>
chore(release): publish 8.58.2</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/be6b49a02d68db9bdc8985e7e9e1598700fda2fa"><code>be6b49a</code></a>
fix: remove tsbuildinfo cache file from published packages (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser/issues/12187">#12187</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/5311ed312eadf4e238324f2726ae0b1f3f2206e6"><code>5311ed3</code></a>
chore(release): publish 8.58.1</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/parser">compare
view</a></li>
</ul>
</details>
<br />

Updates `prettier` from 3.8.1 to 3.8.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/releases">prettier's
releases</a>.</em></p>
<blockquote>
<h2>3.8.3</h2>
<ul>
<li>SCSS: Prevent trailing comma in <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/pull/18471">prettier/prettier#18471</a>
by <a href="https://github.com/kovsu"><code>@​kovsu</code></a>)</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/3.8.3/CHANGELOG.md#383">Changelog</a></p>
<h2>3.8.2</h2>
<ul>
<li>Support Angular v21.2</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#382">Changelog</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's
changelog</a>.</em></p>
<blockquote>
<h1>3.8.3</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.8.2...3.8.3">diff</a></p>
<h4>SCSS: Prevent trailing comma in <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/pull/18471">#18471</a>
by <a href="https://github.com/kovsu"><code>@​kovsu</code></a>)</h4>
<!-- raw HTML omitted -->
<pre lang="scss"><code>// Input
$value: if(sass(false): 1; else: -1);
<p>// Prettier 3.8.2
$value: if(
sass(false): 1; else: -1,
);</p>
<p>// Prettier 3.8.3
$value: if(sass(false): 1; else: -1);
</code></pre></p>
<h1>3.8.2</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.8.1...3.8.2">diff</a></p>
<h4>Angular: Support Angular v21.2 (<a
href="https://redirect.github.com/prettier/prettier/pull/18722">#18722</a>,
<a
href="https://redirect.github.com/prettier/prettier/pull/19034">#19034</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<p>Exhaustive typechecking with <code>@default never;</code></p>
<!-- raw HTML omitted -->
<pre lang="html"><code>&lt;!-- Input --&gt;
@switch (foo) {
  @case (1) {}
  @default never;
}
<p>&lt;!-- Prettier 3.8.1 --&gt;
SyntaxError: Incomplete block &quot;default never&quot;. If you meant to
write the @ character, you should use the &quot;&amp;<a
href="https://redirect.github.com/prettier/prettier/issues/64">#64</a>;&quot;
HTML entity instead. (3:3)</p>
<p>&lt;!-- Prettier 3.8.2 --&gt;
<a href="https://github.com/switch"><code>@​switch</code></a> (foo) {
<a href="https://github.com/case"><code>@​case</code></a> (1) {}
<a href="https://github.com/default"><code>@​default</code></a> never;
}
</code></pre></p>
<p><code>arrow function</code> and <code>instanceof</code>
expressions.</p>
<!-- raw HTML omitted -->
<pre lang="html"><code>&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/prettier/prettier/commit/d7108a79ec745c04292aabf22c4c1adbd690b191"><code>d7108a7</code></a>
Release 3.8.3</li>
<li><a
href="https://github.com/prettier/prettier/commit/177f90898170d363ef64fde663e4d13170688bfe"><code>177f908</code></a>
Prevent trailing comma in SCSS <code>if()</code> function (<a
href="https://redirect.github.com/prettier/prettier/issues/18471">#18471</a>)</li>
<li><a
href="https://github.com/prettier/prettier/commit/1cd40668c3d6f2f4cf9d87bbc9096d92361b2606"><code>1cd4066</code></a>
Release <code>@​prettier/plugin-oxc</code><a
href="https://github.com/0"><code>@​0</code></a>.1.4</li>
<li><a
href="https://github.com/prettier/prettier/commit/a8700e245038cd8cc0cf28ef06ffedbcb3fc2dfc"><code>a8700e2</code></a>
Update oxc-parser to v0.125.0</li>
<li><a
href="https://github.com/prettier/prettier/commit/752157c78eca6f0a30e5d5cb513b682c5ecfa01e"><code>752157c</code></a>
Fix tests</li>
<li><a
href="https://github.com/prettier/prettier/commit/053fd418e180b12fa2014260212fae831f5fc5ec"><code>053fd41</code></a>
Bump Prettier dependency to 3.8.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/904c6365ec46726fd0e21021c52ae934b7e5abc6"><code>904c636</code></a>
Clean changelog_unreleased</li>
<li><a
href="https://github.com/prettier/prettier/commit/dc1f7fcc508d116cbf1644d69a1f0eb93e40d4a4"><code>dc1f7fc</code></a>
Update dependents count</li>
<li><a
href="https://github.com/prettier/prettier/commit/b31557cf331a02acf83e7e29d1001b070189a0d9"><code>b31557c</code></a>
Release 3.8.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/96bbaeda0525bf758e464aed2f939d739a85c315"><code>96bbaed</code></a>
Support Angular v21.2 (<a
href="https://redirect.github.com/prettier/prettier/issues/18722">#18722</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/prettier/prettier/compare/3.8.1...3.8.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `@langchain/core` from 1.1.38 to 1.1.42
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchainjs/releases"><code>@​langchain/core</code>'s
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.41</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10733">#10733</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/589f29ce844eb252c2d5e6b0f8d26de37763a0d7"><code>589f29c</code></a>
Thanks <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>! -
fix(core): Update inheritance behavior for tracer metadata for special
keys</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10711">#10711</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/2e9e6969e248a53ede0659a41d0ac8dbaf291ab4"><code>2e9e696</code></a>
Thanks <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>! -
feat(core): Add chat model and llm invocation params to traced
metadata</p>
</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.40</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10694">#10694</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/d3e080995bb267bf3797067ab53c96bc2a6c8e3f"><code>d3e0809</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
refactor(core): decouple tracer-only metadata defaults from runnable
metadata
<ul>
<li>Add tracer-scoped inheritable metadata/tag options in callback
manager while keeping backward-compatible aliases.</li>
<li>Move configurable-to-tracing metadata derivation into a tracer-only
path and keep <code>ensureConfig</code> metadata mirroring limited to
<code>model</code>.</li>
<li>Update <code>LangChainTracer</code> default metadata/tag handling
and add regression tests for stream events metadata behavior.</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/core</code><a
href="https://github.com/1"><code>@​1</code></a>.1.39</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langchainjs/pull/10430">#10430</a>
<a
href="https://github.com/langchain-ai/langchainjs/commit/d3d0922c24afcd3006fb94dcadd3ebe08fbf2383"><code>d3d0922</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(langchain): support for browser tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langchainjs/commits">compare
view</a></li>
</ul>
</details>
<br />

Updates `@langchain/langgraph` from 1.2.6 to 1.2.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/releases"><code>@​langchain/langgraph</code>'s
releases</a>.</em></p>
<blockquote>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2315">#2315</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
propagate tracer metadata defaults from configurable</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2311">#2311</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
fix: export missing types for typescript 6.0 declaration file
compatibility</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/458d66bf665468854abb8133594d4d4f966054ed"><code>458d66b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.8.9</li>
</ul>
</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2275">#2275</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
enhance runtime with executionInfo and serverInfo</li>
</ul>
<h2><code>@​langchain/langgraph</code><a
href="https://github.com/1"><code>@​1</code></a>.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2281">#2281</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(sdk): support for headless tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/CHANGELOG.md"><code>@​langchain/langgraph</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>1.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2315">#2315</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
Thanks <a href="https://github.com/hntrl"><code>@​hntrl</code></a>! -
propagate tracer metadata defaults from configurable</p>
</li>
<li>
<p><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2311">#2311</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
fix: export missing types for typescript 6.0 declaration file
compatibility</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/langchain-ai/langgraphjs/commit/458d66bf665468854abb8133594d4d4f966054ed"><code>458d66b</code></a>]:</p>
<ul>
<li><code>@​langchain/langgraph-sdk</code><a
href="https://github.com/1"><code>@​1</code></a>.8.9</li>
</ul>
</li>
</ul>
<h2>1.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2275">#2275</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
Thanks <a
href="https://github.com/apps/open-swe"><code>@​open-swe</code></a>! -
enhance runtime with executionInfo and serverInfo</li>
</ul>
<h2>1.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/langchain-ai/langgraphjs/pull/2281">#2281</a>
<a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
Thanks <a
href="https://github.com/christian-bromann"><code>@​christian-bromann</code></a>!
- feat(sdk): support for headless tools</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/aba243a403ffc0795ea95693b88741465e78abec"><code>aba243a</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2313">#2313</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/9102d526c858a4cdbe9b47dcdd062b93da93e49f"><code>9102d52</code></a>
fix(langgraph): propagate tracer metadata defaults from configurable (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2315">#2315</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/b7c196b2142fb888dfcd9ceb1dfb4365d803c8b6"><code>b7c196b</code></a>
fix: export missing types for typescript 6.0 declaration file
compatibility (...</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/9e9807523f751ddcbad8023706563a24806ef50e"><code>9e98075</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2294">#2294</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/e42c2c8836e0b7e36067fea6cc51842e1eb2c60f"><code>e42c2c8</code></a>
feat: enhance runtime with executionInfo and serverInfo (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2275">#2275</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/ed1107df54760e7c8186911064b740d3231398a4"><code>ed1107d</code></a>
chore: version packages (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2283">#2283</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/2b626107101bddb13cf662e1583ea1a828c6e0cd"><code>2b62610</code></a>
feat(sdk): support for headless tools (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2281">#2281</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/05810fe087e1c23d41d3cc495985c8eb91542c83"><code>05810fe</code></a>
Fix Vue subagent grouping during streaming (<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2274">#2274</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/271f9750f0aaaac9ead036d997797b174ec9743c"><code>271f975</code></a>
chore(deps): bump the langchain group across 1 directory with 7 updates
(<a
href="https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core/issues/2270">#2270</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraphjs/commit/d88f29ba2558579781e60307947dba47290d58a1"><code>d88f29b</code></a>
chore(repo): migrate linting and formatting from ESLint/Prettier to
oxlint/ox...</li>
<li>See full diff in <a
href="https://github.com/langchain-ai/langgraphjs/commits/@langchain/langgraph@1.2.9/libs/langgraph-core">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:13:35 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
aa2a370fe4 chore(deps): bump the minor-and-patch group in /libs/checkpoint-conformance with 3 updates (#7667)
Bumps the minor-and-patch group in /libs/checkpoint-conformance with 3
updates:
[langgraph-checkpoint](https://github.com/langchain-ai/langgraph),
[ruff](https://github.com/astral-sh/ruff) and
[ty](https://github.com/astral-sh/ty).

Updates `langgraph-checkpoint` from 4.0.1 to 4.0.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langgraph/releases">langgraph-checkpoint's
releases</a>.</em></p>
<blockquote>
<h2>langgraph-checkpoint==4.0.3</h2>
<p>Changes since checkpoint==4.0.2</p>
<ul>
<li>release(checkpoint): 4.0.3 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7625">#7625</a>)</li>
<li>fix(checkpoint): revive lc=2 JSON blobs for safe types without
allowlist (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7582">#7582</a>)</li>
<li>chore: dedup warnings (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7257">#7257</a>)</li>
<li>chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/checkpoint
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7525">#7525</a>)</li>
</ul>
<h2>langgraph-checkpoint==4.0.2</h2>
<p>Changes since checkpoint==4.0.1</p>
<ul>
<li>release(checkpoint): 4.0.2 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7518">#7518</a>)</li>
<li>docs: document LANGGRAPH_STRICT_MSGPACK for checkpoint security (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7517">#7517</a>)</li>
<li>chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/checkpoint (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7506">#7506</a>)</li>
<li>chore(deps): bump langchain-core from 1.2.23 to 1.2.28 in
/libs/checkpoint (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7453">#7453</a>)</li>
<li>chore(deps): bump the minor-and-patch group in /libs/checkpoint with
3 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7373">#7373</a>)</li>
<li>chore(deps): bump pygments from 2.19.2 to 2.20.0 in /libs/checkpoint
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7348">#7348</a>)</li>
<li>chore(deps): bump the minor-and-patch group in /libs/checkpoint with
2 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7292">#7292</a>)</li>
<li>chore(deps): bump requests from 2.32.5 to 2.33.0 in /libs/checkpoint
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7285">#7285</a>)</li>
<li>chore(deps): bump the all-dependencies group in /libs/checkpoint
with 2 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7250">#7250</a>)</li>
<li>chore(deps): bump the all-dependencies group in /libs/checkpoint
with 2 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7198">#7198</a>)</li>
<li>chore(deps): bump orjson from 3.11.5 to 3.11.6 in /libs/checkpoint
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7146">#7146</a>)</li>
<li>fix(checkpoint): don't add the task to the checkpoint batch if it
was… (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7168">#7168</a>)</li>
<li>chore(deps): bump the all-dependencies group across 1 directory with
4 updates (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7074">#7074</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/372d54dc4ff16b8b32ae3e1534b5e8d90822e2d2"><code>372d54d</code></a>
release(checkpoint): 4.0.3 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7625">#7625</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/f4aee546add9aef6a8f72c8a94f55ec5c824da7d"><code>f4aee54</code></a>
fix(prebuilt): hydrate ToolNode state from channels via pregel helpers
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7594">#7594</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/85cd64ed69e6f4d794804dd3a8feec2d3ead6304"><code>85cd64e</code></a>
fix(checkpoint): revive lc=2 JSON blobs for safe types without allowlist
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7582">#7582</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/53a9806e659552f91adf5bb0f2de5820b87a55a0"><code>53a9806</code></a>
chore(deps): bump nbconvert from 7.17.0 to 7.17.1 in /libs/langgraph (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7573">#7573</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/219fbbe8d01823b790000a809266df24d794709a"><code>219fbbe</code></a>
chore(deps): bump python-dotenv from 1.2.1 to 1.2.2 in /libs/langgraph
(<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7574">#7574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/aeff9549c29c5b6017e6bd6dfe19c1365baace70"><code>aeff954</code></a>
chore: node-level timeouts (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7599">#7599</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/1a248cba45377cd8f8cd02977c85725b6d3c6a5b"><code>1a248cb</code></a>
release(prebuilt): 1.0.11 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7610">#7610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/45246f6c741f677a405f03e119d7f43466cc2a0b"><code>45246f6</code></a>
feat(prebuilt): allow ToolNode tools to return list[Command |
ToolMessage] (#...</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/8657df80f36625490b05a0bfa14e63ac747a8243"><code>8657df8</code></a>
chore: mixup cli formatting (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7585">#7585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langgraph/commit/a529b9bede1f1b6ecada4f0cc0e5c4b83526e563"><code>a529b9b</code></a>
chore(langgraph): bump version 1.1.8 -&gt; 1.1.9 (<a
href="https://redirect.github.com/langchain-ai/langgraph/issues/7563">#7563</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langgraph/compare/checkpoint==4.0.1...checkpoint==4.0.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.8 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>0.15.11</h2>
<p>Released on 2026-04-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Ignore <code>RUF029</code> when function is
decorated with <code>asynccontextmanager</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24642">#24642</a>)</li>
<li>[<code>airflow</code>] Implement
<code>airflow-xcom-pull-in-template-string</code> (<code>AIR201</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/23583">#23583</a>)</li>
<li>[<code>flake8-bandit</code>] Fix <code>S103</code> false positives
and negatives in mask analysis (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24424">#24424</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.8...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `ty` from 0.0.27 to 0.0.33
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/releases">ty's
releases</a>.</em></p>
<blockquote>
<h2>0.0.33</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-28.</p>
<h3>Notable changes</h3>
<ul>
<li>
<p>ty now prefers the declared type of an annotated assignment in more
situations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24802">#24802</a>).
Consider this example:</p>
<pre lang="py"><code>from some_library import untyped_function
<p>threshold: int | None = 0
result: str = untyped_function()
</code></pre></p>
<p>ty previously favored the <em>inferred</em> type of the right hand
side expression when <code>threshold</code> and <code>result</code> were
used. This is useful for <code>threshold</code>, as it allows something
like <code>threshold += 1</code> to work without an error: we know that
<code>threshold</code> could later become <code>None</code>, but
<em>right now</em>, we see that it is an <code>int</code>. However, for
<code>result</code>, the inferred type is <code>Unknown</code>. This is
<em>not</em> a useful type and it can lead to false negatives. Starting
with this release, ty will therefore prefer
the declared type <em>if the inferred and declared types are mutually
assignable</em>. In the above example, <code>threshold</code> will still
be inferred as <code>int</code> (or rather <code>Literal[1]</code>), but
<code>result</code> will now be inferred as <code>str</code>. If you
previously added <code>cast</code>s to work around this behavior, you
should be able to remove them after upgrading.</p>
</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix reporting of annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24811">#24811</a>)</li>
<li>Fix project and workspace selection (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24824">#24824</a>)</li>
<li>Fix go-to definition for generic classes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24714">#24714</a>)</li>
<li>Fix receiver coloring for aliased decorators (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24884">#24884</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Add support for go-to definition in literal enum member inlay hints
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24792">#24792</a>)</li>
<li>Add support for &quot;baking&quot; keyword argument inlay hints into
the source code (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24667">#24667</a>)</li>
<li>Don't allow inlay hint edits when introducing a non global scope
symbol (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24797">#24797</a>)</li>
<li>Omit semantic highlighting for unresolved symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24718">#24718</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Support narrowing with aliased conditional expressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24302">#24302</a>)</li>
<li>Model short-circuiting control flow in Boolean expressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24458">#24458</a>)</li>
<li>Handle <code>finally</code> blocks where all
<code>try</code>/<code>except</code> blocks are terminal (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24882">#24882</a>)</li>
<li>Detect invalid <code>ClassVar</code> vs instance-attribute overrides
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24767">#24767</a>)</li>
<li>Emit diagnostic for invalid uses of <code>Unpack[...]</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24868">#24868</a>)</li>
<li>Infer lambda parameter types with <code>Callable</code> type context
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24317">#24317</a>)</li>
<li>Support <code>**</code> unpacking of <code>TypedDict</code> in
dict-literal assignments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24703">#24703</a>)</li>
<li>Support <code>Unpack[TypedDict]</code> in <code>**kwargs</code>
signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24653">#24653</a>)</li>
<li>Treat <code>[*xs]</code> as an irrefutable pattern when matching on
<code>Sequence</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24787">#24787</a>)</li>
<li>Improve generics solving for unions in invariant positions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24698">#24698</a>)</li>
<li>Improve generics solving for unions when matching against protocols
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24837">#24837</a>)</li>
</ul>
<h3>Diagnostics</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/blob/main/CHANGELOG.md">ty's
changelog</a>.</em></p>
<blockquote>
<h2>0.0.33</h2>
<p>Released on 2026-04-28.</p>
<h3>Notable changes</h3>
<ul>
<li>
<p>ty now prefers the declared type of an annotated assignment in more
situations (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24802">#24802</a>).
Consider this example:</p>
<pre lang="py"><code>from some_library import untyped_function
<p>threshold: int | None = 0
result: str = untyped_function()
</code></pre></p>
<p>ty previously favored the <em>inferred</em> type of the right hand
side expression when <code>threshold</code> and <code>result</code> were
used. This is useful for <code>threshold</code>, as it allows something
like <code>threshold += 1</code> to work without an error: we know that
<code>threshold</code> could later become <code>None</code>, but
<em>right now</em>, we see that it is an <code>int</code>. However, for
<code>result</code>, the inferred type is <code>Unknown</code>. This is
<em>not</em> a useful type and it can lead to false negatives. Starting
with this release, ty will therefore prefer
the declared type <em>if the inferred and declared types are mutually
assignable</em>. In the above example, <code>threshold</code> will still
be inferred as <code>int</code> (or rather <code>Literal[1]</code>), but
<code>result</code> will now be inferred as <code>str</code>. If you
previously added <code>cast</code>s to work around this behavior, you
should be able to remove them after upgrading.</p>
</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Fix reporting of annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24811">#24811</a>)</li>
<li>Fix project and workspace selection (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24824">#24824</a>)</li>
<li>Fix go-to definition for generic classes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24714">#24714</a>)</li>
<li>Fix receiver coloring for aliased decorators (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24884">#24884</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Add support for go-to definition in literal enum member inlay hints
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24792">#24792</a>)</li>
<li>Add support for &quot;baking&quot; keyword argument inlay hints into
the source code (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24667">#24667</a>)</li>
<li>Don't allow inlay hint edits when introducing a non global scope
symbol (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24797">#24797</a>)</li>
<li>Omit semantic highlighting for unresolved symbols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24718">#24718</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Support narrowing with aliased conditional expressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24302">#24302</a>)</li>
<li>Model short-circuiting control flow in Boolean expressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24458">#24458</a>)</li>
<li>Handle <code>finally</code> blocks where all
<code>try</code>/<code>except</code> blocks are terminal (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24882">#24882</a>)</li>
<li>Detect invalid <code>ClassVar</code> vs instance-attribute overrides
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24767">#24767</a>)</li>
<li>Emit diagnostic for invalid uses of <code>Unpack[...]</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24868">#24868</a>)</li>
<li>Infer lambda parameter types with <code>Callable</code> type context
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24317">#24317</a>)</li>
<li>Support <code>**</code> unpacking of <code>TypedDict</code> in
dict-literal assignments (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24703">#24703</a>)</li>
<li>Support <code>Unpack[TypedDict]</code> in <code>**kwargs</code>
signatures (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24653">#24653</a>)</li>
<li>Treat <code>[*xs]</code> as an irrefutable pattern when matching on
<code>Sequence</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24787">#24787</a>)</li>
<li>Improve generics solving for unions in invariant positions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24698">#24698</a>)</li>
<li>Improve generics solving for unions when matching against protocols
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24837">#24837</a>)</li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Add error context to <code>invalid-return-type</code> diagnostics,
<code>invalid-yield</code> diagnostics, attribute assignment diagnostics
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24770">#24770</a>,
<a
href="https://redirect.github.com/astral-sh/ruff/pull/24771">#24771</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ty/commit/c512d8425418a2170e92aa7fbbd70952d4e04118"><code>c512d84</code></a>
Bump version to 0.0.33 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3368">#3368</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/4cd7b334b90eba09042700e7b654044c2d6bcd15"><code>4cd7b33</code></a>
Upgrade Depot runners from macOS 14 to 15 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3363">#3363</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/c78b8324515bf15a662f61e1d74fd85484d67e8c"><code>c78b832</code></a>
Update rui314/setup-mold digest to 9c9c13b (<a
href="https://redirect.github.com/astral-sh/ty/issues/3342">#3342</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/dea338134aef96d741a48886e414f622dbf10426"><code>dea3381</code></a>
Update actions/cache action to v5.0.5 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3343">#3343</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/d451af477bd5517e132609c3b016799d697f4182"><code>d451af4</code></a>
update typing-features and faqs (<a
href="https://redirect.github.com/astral-sh/ty/issues/3335">#3335</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/052d70bc1a7457f50c5acbafe8de7a846f7d5e77"><code>052d70b</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ty/issues/3344">#3344</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/66b5e878163ce4ab4810a45a2679a98011932b8b"><code>66b5e87</code></a>
Update astral-sh/setup-uv action to v8.1.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3345">#3345</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/7ec6712a6f02d0255de4c7f0066d5bb23987a9bb"><code>7ec6712</code></a>
Add a 'Diagnostics improvements' section to the changelogs (<a
href="https://redirect.github.com/astral-sh/ty/issues/3309">#3309</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/978dfdb38dfb568943f73779d769a965c1d5a397"><code>978dfdb</code></a>
Add version metadata publishing to the release process (<a
href="https://redirect.github.com/astral-sh/ty/issues/3292">#3292</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/4d1e1fc57ca8bfdcbcee513ba92135d2932eb279"><code>4d1e1fc</code></a>
Bump version to 0.0.32 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3302">#3302</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ty/compare/0.0.27...0.0.33">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:12:31 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
67cd5b7718 chore(deps): bump notebook from 7.5.2 to 7.5.6 in /libs/langgraph (#7657)
Bumps [notebook](https://github.com/jupyter/notebook) from 7.5.2 to
7.5.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jupyter/notebook/releases">notebook's
releases</a>.</em></p>
<blockquote>
<h2>v7.5.6</h2>
<h2>7.5.6</h2>
<p>(<a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/application-extension@7.5.5...2e642f0cb10be314ba5d97d709cffe41bf992d9e">Full
Changelog</a>)</p>
<h3>Security patches</h3>
<ul>
<li>CVE-2026-42557 <a
href="https://github.com/jupyterlab/jupyterlab/security/advisories/GHSA-mqcg-5x36-vfcg">https://github.com/jupyterlab/jupyterlab/security/advisories/GHSA-mqcg-5x36-vfcg</a></li>
<li>CVE-2026-40171 <a
href="https://github.com/jupyter/notebook/security/advisories/GHSA-rch3-82jr-f9w9">https://github.com/jupyter/notebook/security/advisories/GHSA-rch3-82jr-f9w9</a></li>
</ul>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>Update to JupyterLab v4.5.7 <a
href="https://redirect.github.com/jupyter/notebook/pull/7902">#7902</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
</ul>
<h3>Documentation improvements</h3>
<ul>
<li>docs: Fix broken links in troubleshooting and migration docs <a
href="https://redirect.github.com/jupyter/notebook/pull/7824">#7824</a>
(<a
href="https://github.com/RamiNoodle733"><code>@​RamiNoodle733</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/notebook/graphs/contributors?from=2026-03-11&amp;to=2026-04-30&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/jtpio"><code>@​jtpio</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2026-03-11..2026-04-30&amp;type=Issues">activity</a>)
| <a
href="https://github.com/RamiNoodle733"><code>@​RamiNoodle733</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARamiNoodle733+updated%3A2026-03-11..2026-04-30&amp;type=Issues">activity</a>)</p>
<h2>v7.5.5</h2>
<h2>7.5.5</h2>
<p>(<a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/application-extension@7.5.4...4f8438b0c67dc4f010bf8cd052da4f16e2ed3828">Full
Changelog</a>)</p>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>Update to JupyterLab v4.5.6 <a
href="https://redirect.github.com/jupyter/notebook/pull/7861">#7861</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
<li>[7.5.x] Drop Python 3.9 on CI <a
href="https://redirect.github.com/jupyter/notebook/pull/7860">#7860</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
<li>Fix check links <a
href="https://redirect.github.com/jupyter/notebook/pull/7857">#7857</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/notebook/graphs/contributors?from=2026-02-24&amp;to=2026-03-11&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/jtpio"><code>@​jtpio</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2026-02-24..2026-03-11&amp;type=Issues">activity</a>)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jupyter/notebook/blob/@jupyter-notebook/tree@7.5.6/CHANGELOG.md">notebook's
changelog</a>.</em></p>
<blockquote>
<h2>7.5.6</h2>
<p>(<a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/application-extension@7.5.5...2e642f0cb10be314ba5d97d709cffe41bf992d9e">Full
Changelog</a>)</p>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>Update to JupyterLab v4.5.7 <a
href="https://redirect.github.com/jupyter/notebook/pull/7902">#7902</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
</ul>
<h3>Documentation improvements</h3>
<ul>
<li>docs: Fix broken links in troubleshooting and migration docs <a
href="https://redirect.github.com/jupyter/notebook/pull/7824">#7824</a>
(<a
href="https://github.com/RamiNoodle733"><code>@​RamiNoodle733</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/notebook/graphs/contributors?from=2026-03-11&amp;to=2026-04-30&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/jtpio"><code>@​jtpio</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2026-03-11..2026-04-30&amp;type=Issues">activity</a>)
| <a
href="https://github.com/RamiNoodle733"><code>@​RamiNoodle733</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3ARamiNoodle733+updated%3A2026-03-11..2026-04-30&amp;type=Issues">activity</a>)</p>
<!-- raw HTML omitted -->
<h2>7.5.5</h2>
<p>(<a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/application-extension@7.5.4...4f8438b0c67dc4f010bf8cd052da4f16e2ed3828">Full
Changelog</a>)</p>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>Update to JupyterLab v4.5.6 <a
href="https://redirect.github.com/jupyter/notebook/pull/7861">#7861</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
<li>[7.5.x] Drop Python 3.9 on CI <a
href="https://redirect.github.com/jupyter/notebook/pull/7860">#7860</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
<li>Fix check links <a
href="https://redirect.github.com/jupyter/notebook/pull/7857">#7857</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/notebook/graphs/contributors?from=2026-02-24&amp;to=2026-03-11&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/jtpio"><code>@​jtpio</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnotebook+involves%3Ajtpio+updated%3A2026-02-24..2026-03-11&amp;type=Issues">activity</a>)</p>
<h2>7.5.4</h2>
<p>(<a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/application-extension@7.5.3...e5d8418b706fcefd4208bb61c22399dd3123555b">Full
Changelog</a>)</p>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>Update to JupyterLab v4.5.5 <a
href="https://redirect.github.com/jupyter/notebook/pull/7842">#7842</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
<li>Fix PyO3 CI failure with Python 3.15 <a
href="https://redirect.github.com/jupyter/notebook/pull/7836">#7836</a>
(<a href="https://github.com/jtpio"><code>@​jtpio</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/jupyter/notebook/commit/1ab2d2b99261996e94069ca53dd3d74b8b2ee1ba"><code>1ab2d2b</code></a>
Publish 7.5.6</li>
<li><a
href="https://github.com/jupyter/notebook/commit/50e5222c9670121c3369900c7dce01aae53823fc"><code>50e5222</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/jupyter/notebook/commit/2e642f0cb10be314ba5d97d709cffe41bf992d9e"><code>2e642f0</code></a>
Update to JupyterLab v4.5.7 (<a
href="https://redirect.github.com/jupyter/notebook/issues/7902">#7902</a>)</li>
<li><a
href="https://github.com/jupyter/notebook/commit/4b93f98b5a6e57027a2e1d58694b56e2ebd793a3"><code>4b93f98</code></a>
Backport PR <a
href="https://redirect.github.com/jupyter/notebook/issues/7824">#7824</a>:
docs: Fix broken links in troubleshooting and migration do...</li>
<li><a
href="https://github.com/jupyter/notebook/commit/9a2c88fe646bac05b39dbe53e3e0ce95cafee016"><code>9a2c88f</code></a>
Publish 7.5.5</li>
<li><a
href="https://github.com/jupyter/notebook/commit/4f8438b0c67dc4f010bf8cd052da4f16e2ed3828"><code>4f8438b</code></a>
Update to JupyterLab v4.5.6 (<a
href="https://redirect.github.com/jupyter/notebook/issues/7861">#7861</a>)</li>
<li><a
href="https://github.com/jupyter/notebook/commit/f78fcfada85f9e4b46003bc1b831c83e6f4c30b3"><code>f78fcfa</code></a>
Backport PR <a
href="https://redirect.github.com/jupyter/notebook/issues/7857">#7857</a>:
Fix check links (<a
href="https://redirect.github.com/jupyter/notebook/issues/7858">#7858</a>)</li>
<li><a
href="https://github.com/jupyter/notebook/commit/9e4cf2a44594e650e1ae3da49f81ae420135f32f"><code>9e4cf2a</code></a>
[7.5.x] Drop Python 3.9 on CI (<a
href="https://redirect.github.com/jupyter/notebook/issues/7860">#7860</a>)</li>
<li><a
href="https://github.com/jupyter/notebook/commit/ecc3aaf1bbf8f9cbec9c5d85df79db0f62b6d1e6"><code>ecc3aaf</code></a>
Publish 7.5.4</li>
<li><a
href="https://github.com/jupyter/notebook/commit/e5d8418b706fcefd4208bb61c22399dd3123555b"><code>e5d8418</code></a>
Update to JupyterLab v4.5.5 (<a
href="https://redirect.github.com/jupyter/notebook/issues/7842">#7842</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/jupyter/notebook/compare/@jupyter-notebook/tree@7.5.2...@jupyter-notebook/tree@7.5.6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=notebook&package-manager=uv&previous-version=7.5.2&new-version=7.5.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 23:11:51 -07:00
Nick HollonandGitHub b82d380634 release: alpha bump prebuilt 1.1.0a2, langgraph 1.2.0a5 (#7682) 2026-05-01 13:56:21 -04:00
afdfbd55da fix(langgraph): coerce dict/str writes in _messages_delta_reducer (#7680)
## Problem

`_messages_delta_reducer` assumed writes always contain pre-typed
`BaseMessage` objects (as noted in its docstring). In practice,
HTTP-driven graphs always receive message input as JSON dicts — the same
way `add_messages` receives them. Using
`DeltaChannel(_messages_delta_reducer)` with any HTTP input would crash
with:

```
AttributeError: 'dict' object has no attribute 'id'
```

This makes `_messages_delta_reducer` unusable for the primary motivating
use-case (replacing `add_messages` in production LLM graphs).

## Fix

Mirror the coercion contract of `add_messages`:
- Regular message dicts (`{"role": "human", "content": "..."}`) →
`convert_to_messages`
- `RemoveMessage` dicts (`{"type": "remove", "id": "..."}`) →
`RemoveMessage` directly (langchain_core's `convert_to_messages` doesn't
support this format)
- `BaseMessage` objects → pass through unchanged
- Lists/sequences → element-wise coercion of the above

The fix is a small `_coerce_one` + `_to_msgs` helper pair that replaces
the previous `[w] if isinstance(w, BaseMessage) else w` generator.

## Tests

Added `test_delta_channel_dict_coercion` in `test_channels.py` covering:
- dict append via `{"role": "human", "content": ..., "id": ...}`
- dict update-in-place (same ID)
- `{"type": "remove", "id": ...}` tombstoning

All 23 existing delta-channel tests still pass.

Release Notes: None

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:56:01 -04:00
Nick HollonandGitHub 15113c0f60 fix(prebuilt): scope ToolCallTransformer projection to its own namespace (#7681) 2026-05-01 13:29:42 -04:00
Nick HollonandGitHub 85bca24635 release: alpha bump prebuilt 1.1.0a1, langgraph 1.2.0a4 (#7679) 2026-05-01 11:55:05 -04:00
Nick HollonandGitHub f2bd3224f0 feat(langgraph): dispatch stream_events(version='v3') on Pregel (#7677) 2026-05-01 11:30:32 -04:00
Sydney RunkleandGitHub 530fcabfc3 release: alpha bump (a3) for langgraph, checkpoint, checkpoint-postgres (#7678)
## Summary
- Bumps `langgraph` 1.2.0a1 → 1.2.0a3
- Bumps `langgraph-checkpoint` 4.1.0a1 → 4.1.0a3
- Bumps `langgraph-checkpoint-postgres` 3.1.0a1 → 3.1.0a3
- Bumps min `langgraph-checkpoint` constraint in `langgraph` to
`>=4.1.0a3`
- Refreshes uv locks across the workspace

(Note: `a2` was already cut from another branch.)

## Test plan
- [ ] CI passes
2026-05-01 11:18:52 -04:00
d8b7800183 chore(langgraph): use two phase read to avoid unnecessary data transport (#7660)
## Summary

Replaces the single-roundtrip `UNION ALL` DeltaChannel read with a
two-stage query that avoids fetching unused snapshot blobs, then removes
the old combined path entirely.

### Problem

`_get_channel_writes_history` used a single `UNION ALL` query that
fetched **all** checkpoint metadata, writes, and blobs for a
`(thread_id, channel)` in one shot. With `snapshot_frequency=N`, this
pulled back O(N/freq) full-size snapshot blobs even though only the
nearest one is needed to seed reconstruction. At 500 turns with
`snapshot_frequency=10`, this meant fetching ~100 complete
message-history snapshots per read.

### Solution

Two-stage read:
- **Stage 1** — lightweight scan of `checkpoints` only (no blob bytes):
walks the parent chain from the target checkpoint and stops at the first
ancestor with a snapshot, returning `chain_cids` and `seed_version`
- **Stage 2** — targeted fetch: only the writes for `chain_cids` and the
single seed blob at `seed_version`

The two-stage path is now unconditional — the old combined query and
`LG_DELTA_TWO_STAGE_QUERY` env-var gate have been removed.

### Sentinel cleanup

`DELTA_SENTINEL` is now a pure in-memory signal and is never written to
storage:
- Postgres `put()` already stripped it from `channel_values` before
writing blobs
- Memory saver `put()` now stores `"empty"` instead of serializing the
sentinel
- `EXT_DELTA_SENTINEL` (msgpack ext code 8) removed from
`JsonPlusSerializer`
- `DELTA_SENTINEL` is kept as an in-memory marker:
`DeltaChannel.checkpoint()` returns it so savers know to skip it, and
`_ChannelWritesHistory.seed` uses it to mean "no snapshot found, start
from empty"

## Performance

Benchmarked at `snapshot_frequency=10` on Postgres (`~100 tok/msg`):

| turns | old combined query | two-stage |
|------:|-------------------:|----------:|
| 50    | 6.0ms              | 2.8ms  (2.1x faster) |
| 100   | 10.1ms             | 5.6ms  (1.8x faster) |
| 500   | **216.1ms**        | 15.3ms (**14x faster**) |

The old query's read time grew super-linearly with turn count because
each read fetched O(N/freq) full snapshot blobs. Two-stage keeps read
depth bounded by `snapshot_frequency` regardless of thread length.

## Test plan

- `make test` in `libs/checkpoint`, `libs/checkpoint-postgres`,
`libs/langgraph`
- Removed `test_delta_sentinel_serde_round_trip` (sentinel no longer
serializable)
- Updated `test_memory.py` — delta channel blobs stored as `"empty"`,
not serialized sentinel
- Updated `test_channels.py` — `channel_values` no longer contains
sentinel key for DeltaChannels
- Deleted `test_delta_channel_two_stage_benchmark.py` (one-stage vs
two-stage comparison; path no longer exists)

---------

Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 11:06:54 -04:00
Quanzheng LongandGitHub c8c58a0768 fix(langgraph): make NodeTimeoutError retryable by default (#7659)
## Summary

`NodeTimeoutError` previously inherited from `TimeoutError`, which is a
subclass of `OSError`. Since `OSError` is in the default `RetryPolicy`
blocklist, timeout errors from `TimeoutPolicy` were silently **not
retried** unless the user explicitly set `retry_on=NodeTimeoutError`.

This PR changes `NodeTimeoutError` to inherit from `Exception` directly,
so that the default `RetryPolicy` treats it as retryable — matching user
expectations when both `RetryPolicy` and `TimeoutPolicy` are configured
together.

- Change `NodeTimeoutError(TimeoutError)` →
`NodeTimeoutError(Exception)`
- Add test asserting `NodeTimeoutError` is retryable with the default
policy
- Add observer-ordering tests pinning down `finish=error` emission
timing relative to retry backoff, error handler start, and retry
exhaustion

## Breaking change

Code that catches `NodeTimeoutError` via `except TimeoutError` or
`except OSError` will no longer match. Use `except NodeTimeoutError`
instead.

## Test plan

- [x] `test_should_retry_default_retry_on` — asserts `NodeTimeoutError`
is retryable with default `RetryPolicy()`
- [x] Existing timeout+retry tests continue to pass (`test_retry.py`)
2026-04-30 12:17:13 -07:00
Nick HollonandGitHub de9b7c61c3 fix(langgraph): arrival-ordered interleave for StreamChannel projections (#7643) 2026-04-30 10:41:43 -04:00
Quanzheng LongandGitHub 63d861165f feat(langgraph): add node-level error handlers (#7233) 2026-04-29 21:27:06 -07:00
Eugene YurtsevandGitHub 9c1d65695e fix(prebuilt): default ToolRuntime tools to empty list (#7650)
Makes `ToolRuntime.tools` default to an empty list when not provided,
which avoids requiring callers and tests to pass it explicitly. Adds a
focused regression test covering direct `ToolRuntime` construction
without `tools`.

Created with [Deep Agents
CLI](https://docs.langchain.com/oss/python/deepagents/cli/overview)
using gpt-5.4 (provider: openai).
2026-04-30 01:07:00 +00:00
40ab009c62 feat: allow graph to graceful shutdown/drain by request (#7274)
## Summary

Adds cooperative drain support for Pregel runs so a graph can be asked
to stop at the next superstep boundary, persist its checkpoint, and
surface a resumable terminal exception.

- New `RunControl` (in `langgraph.runtime`) — a thread-safe handle whose
`request_drain(reason="shutdown")` sets a single flag.
- New `GraphDrained(GraphBubbleUp)` exception (in `langgraph.errors`)
raised when a run exits early due to drain. Carries the `reason` string.
- New `control: RunControl | None` kwarg on `invoke` / `ainvoke` /
`stream` / `astream` / `stream_v2` / `astream_v2`. Wired through to
`Runtime.control`, so nodes can read `runtime.control.drain_requested` /
`drain_reason` and even call `request_drain()` from inside a node.
- Stream transformers learn `"drained"` as a terminal `SubgraphStatus`.

The intended use is hooking SIGTERM (or any external supervisor signal)
to `control.request_drain("sigterm")` so an in-flight graph run can stop
cleanly and be resumed later from the saved checkpoint.

## Semantics: cooperative, between-superstep

`request_drain()` flips a flag. The Pregel loop checks it at the top of
each `tick()`, **after** the previous superstep's writes have been
applied and checkpointed. It never preempts work that is already
running.

| Scenario | Behavior |
|---|---|
| Node mid-execution (blocking I/O, sleeps, etc.) | Runs to completion.
Drain takes effect on the next superstep. |
| Node with a retry policy currently retrying | Retry loop runs to
exhaustion or success (drain is not checked between retries). Drain
takes effect on the next superstep. |
| Functional API: `@entrypoint` with pending `@task` futures |
Entrypoint and all dispatched tasks complete; drain takes effect after
the entrypoint returns. |
| Graph naturally finishes on the same tick where drain was requested
(no more tasks) | Treated as `done`; returns normally. **No
`GraphDrained` is raised.** The caller can inspect
`control.drain_requested` afterwards to distinguish a
drained-but-completed run from a normal one. |
| More tasks remain | Raises `GraphDrained(reason)`. The checkpoint of
the last completed superstep is saved (also under `durability="exit"`).
Resume with `invoke(None, config)` / `ainvoke(None, config)`. |
| Subgraph requests drain | `GraphDrained` bubbles up through the parent
loop and stops it at its own next superstep boundary; the parent's
checkpoint is saved and resumable. |

Drain does **not** cancel asyncio tasks or kill threads. Pair it with a
graceful timeout + `task.cancel()` (or process exit) if you need a hard
upper bound — see `test_drain_then_cancel_after_graceful_timeout` for
the recommended pattern.

## Usage

```python
from langgraph.runtime import RunControl
from langgraph.errors import GraphDrained

control = RunControl()

# In a signal handler, supervisor, etc.:
# control.request_drain("sigterm")

try:
    result = graph.invoke(input, config, control=control)
    if control.drain_requested:
        # finished naturally on the same tick where drain was requested
        ...
except GraphDrained as e:
    # checkpoint saved; resume later with the same config
    log.info("graph drained: %s", e.reason)
```

## Test plan

- [x] Sync + async drain stops the next superstep
(`test_run_control_request_drain_stops_future_steps[_async]`)
- [x] Drain on the terminal step finishes normally
(`test_drain_requested_in_terminal_step_finishes_normally[_async]`)
- [x] `durability=\"exit\"` persists a resumable checkpoint on drain
(`test_drain_with_exit_durability_persists_resume_checkpoint`)
- [x] Subgraph drain bubbles up and parent resumes correctly
(`test_drain_from_subgraph_can_resume_parent`)
- [x] External thread / task triggering drain mid-run
(`test_external_drain_concurrent_sync` / `_async`)
- [x] Drain + hard cancel after graceful timeout
(`test_drain_then_cancel_after_graceful_timeout`)
- [x] Functional API: in-flight `@task` futures still resolve after
`request_drain()`
(`test_request_drain_allows_inflight_[a]call_scheduling`)
- [x] `control` kwarg wired through `stream_v2`
(`test_stream_v2_accepts_control_for_drain`)
- [x] `Runtime.merge` preserves `control`
(`test_merge_runtime_preserves_run_control`)

---------

Co-authored-by: Quanzheng Long <long@langchain.dev>
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:23:31 -07:00
Sydney RunkleandGitHub f95d2309f9 release(checkpoint-postgres): pin to checkpoint 4.1.0a1 (#7648)
## Summary
- Bumps `langgraph-checkpoint-postgres`'s pin on `langgraph-checkpoint`
from `>=4.0.3,<5.0.0` to `>=4.1.0a1,<5.0.0` so the postgres alpha
(3.1.0a1) requires the matching checkpoint alpha released in #7647.
- Mirrors the same pin update applied to `langgraph` (1.2.0a1) on
`wfh/releases/timers`.

## Why
The three alphas (checkpoint 4.1.0a1, checkpoint-postgres 3.1.0a1,
langgraph 1.2.0a1) are meant to be tested as a coherent set. `langgraph`
already pins `>=4.1.0a1`; checkpoint-postgres was missed in that release
and is still letting resolvers fall back to 4.0.x.

## Test plan
- [ ] CI green
- [ ] `uv lock` resolves cleanly across libs (verified locally via `make
lock` — no lock file changes since editable paths already resolved to
4.1.0a1)
2026-04-29 18:00:47 -04:00
William FHGitHubWill Fu-HinthornSydney Runklecopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
4a5765dd23 release: alpha for timers (#7647)
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-29 17:49:01 -04:00
5c18bde0f8 feat(langgraph): DeltaChannel: store sentinel in blobs, reconstruct from checkpoint_writes (#7586)
# DeltaChannel: sentinel-based checkpoint blobs + write-replay
reconstruction

## Summary

`DeltaChannel` is a new fold-reducer channel that stores only a
zero-byte sentinel in checkpoint blobs instead of the full accumulated
value. On restore, the runtime replays ancestor writes through the
reducer to reconstruct state. For long-running threads with large
accumulating state (e.g. message histories), this delivers dramatically
smaller checkpoint blobs with configurable read-depth bounds.

```python
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.graph.message import _messages_delta_reducer

class State(TypedDict):
    # blob per step: ~60 bytes (sentinel) instead of growing full list
    messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
    # bound read depth to 10 steps via periodic snapshots
    messages_bounded: Annotated[list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=10)]
```

---

## Storage benchmarks (InMemory, ~400 char/msg)

**Messages blob storage** (`checkpoint_blobs` bytes for the messages
channel):

| turns | add\_messages | delta(inf) | delta(freq=50) | delta(freq=10) |
delta(freq=5) |

|------:|-------------:|-----------:|---------------:|---------------:|--------------:|
| 10 | 91.0 KB | 60 B (1517x) | 60 B (1517x) | 14.4 KB (6x) | 32.6 KB
(3x) |
| 50 | 2.20 MB | 300 B (7347x) | 67.1 KB (33x) | 423 KB (5x) | 864 KB
(3x) |
| 100 | 8.78 MB | 600 B (14636x) | 310 KB (28x) | 1.72 MB (5x) | 3.48 MB
(3x) |
| 250 | 54.80 MB | 1.5 KB (36536x) | 2.09 MB (26x) | 10.87 MB (5x) |
21.84 MB (3x) |
| 500 | 219.19 MB | 3.0 KB (73063x) | 8.56 MB (26x) | 43.67 MB (5x) |
87.50 MB (3x) |

**Total checkpoint storage** (blobs + writes + metadata):

| turns | add\_messages | delta(inf) | delta(freq=50) | delta(freq=10) |
delta(freq=5) |

|------:|-------------:|-----------:|---------------:|---------------:|--------------:|
| 10 | 129.7 KB | 38.7 KB (3.4x) | 38.7 KB (3.4x) | 53.1 KB (2.4x) |
71.2 KB (1.8x) |
| 50 | 2.40 MB | 196 KB (12x) | 263 KB (9x) | 620 KB (3.9x) | 1.06 MB
(2.3x) |
| 100 | 9.18 MB | 394 KB (23x) | 703 KB (13x) | 2.12 MB (4.3x) | 3.87 MB
(2.4x) |
| 250 | 55.79 MB | 987 KB (57x) | 3.07 MB (18x) | 11.86 MB (4.7x) |
22.82 MB (2.4x) |
| 500 | 221.16 MB | 1.98 MB (112x) | 10.53 MB (21x) | 45.64 MB (4.9x) |
89.48 MB (2.5x) |

**Write-phase peak heap**:

| turns | add\_messages | delta(inf) | delta(freq=50) | delta(freq=10) |
delta(freq=5) |

|------:|-------------:|-----------:|---------------:|---------------:|--------------:|
| 10 | 456 KB | 199 KB (2.3x) | 199 KB (2.3x) | 212 KB (2.2x) | 232 KB
(2.0x) |
| 50 | 3.04 MB | 742 KB (4.1x) | 805 KB (3.8x) | 1.21 MB (2.5x) | 1.67
MB (1.8x) |
| 100 | 10.70 MB | 1.41 MB (7.6x) | 1.82 MB (5.9x) | 3.42 MB (3.1x) |
5.25 MB (2.0x) |
| 250 | 60.44 MB | 3.36 MB (18x) | 5.67 MB (11x) | 14.87 MB (4.1x) |
26.31 MB (2.3x) |

**Read-phase avg `get_state` latency** (5 calls, InMemory):

| turns | add\_messages | delta(inf) | delta(freq=50) | delta(freq=10) |
delta(freq=5) |

|------:|-------------:|-----------:|---------------:|---------------:|--------------:|
| 10 | 0.7 ms | 1.1 ms (0.6x) | 1.1 ms (0.6x) | 0.8 ms (0.9x) | 0.6 ms
(1.1x) |
| 50 | 2.7 ms | 5.3 ms (0.5x) | 3.5 ms (0.8x) | 2.7 ms (1.0x) | 2.7 ms
(1.0x) |
| 100 | 5.5 ms | 11.1 ms (0.5x) | 6.0 ms (0.9x) | 5.2 ms (1.1x) | 5.4 ms
(1.0x) |
| 250 | 12.9 ms | 27.2 ms (0.5x) | 13.6 ms (0.9x) | 12.9 ms (1.0x) |
13.0 ms (1.0x) |

**Postgres `get_tuple` read latency** (~100 tok/msg per step):

| steps | full-list | delta(inf) | delta(freq=50) | delta(freq=10) |
delta(freq=5) |

|------:|----------:|-----------:|---------------:|---------------:|--------------:|
| 10 | 0.29 ms | 0.21 ms (1.4x) | 0.19 ms (1.6x) | 0.19 ms (1.5x) | 0.19
ms (1.6x) |
| 50 | 0.19 ms | 0.15 ms (1.3x) | 0.19 ms (1.0x) | 0.22 ms (0.8x) | 0.29
ms (0.7x) |
| 100 | 0.27 ms | 0.17 ms (1.6x) | 0.22 ms (1.2x) | 0.23 ms (1.2x) |
0.21 ms (1.3x) |
| 500 | 0.60 ms | 0.30 ms (2.0x) | 0.66 ms (0.9x) | 0.56 ms (1.1x) |
0.69 ms (0.9x) |

**Takeaway:** `snapshot_frequency=10` matches full-list read latency
while still saving 5x on blob storage and ~4x on total storage.

---

## How it works

### Checkpoint blobs

`checkpoint()` always returns `DELTA_SENTINEL` (a zero-byte msgpack ext
marker) instead of the accumulated value. On restore, the saver's
`_get_channel_writes_history` walks the ancestor chain collecting
`checkpoint_writes` entries and replays them through the reducer:

```python
# blob stored per step: ~1 byte (sentinel)
# vs. full list growing O(N) every step with BinaryOperatorAggregate
```

### Reducer interface

`DeltaChannel` takes a **batch reducer** `(state, list[writes]) ->
state` — all writes for a step arrive in one call, enabling single-pass
implementations:

```python
#  Don't use add_messages directly — it's a binary operator, not a batch reducer
messages: Annotated[list, DeltaChannel(add_messages)]  # wrong

#  Use _messages_delta_reducer — single pass, dedup by ID, RemoveMessage support
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]

#  Or write your own batch reducer for custom types
def my_dict_reducer(state: dict, writes: list[dict]) -> dict:
    result = dict(state)
    for w in writes:
        result.update(w)
    return result

files: Annotated[dict, DeltaChannel(my_dict_reducer)]
```

### Snapshot frequency

`snapshot_frequency=N` writes a full `_DeltaSnapshot` blob every N
pregel steps, bounding replay depth regardless of thread length.
Snapshots are eager — written even if the channel had no update that
step, so the depth bound always holds:

```python
# Replay walks at most 10 ancestors before hitting a snapshot
messages: Annotated[list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=10)]
```

### Migration from `BinaryOperatorAggregate`

Pre-existing threads written under `BinaryOperatorAggregate` work
transparently after swapping the annotation — the saver detects a
plain-value ancestor blob and uses it as the reconstruction seed:

```python
# Before: BinaryOperatorAggregate stores full list every step
items: Annotated[list, add_messages]

# After: DeltaChannel — existing checkpoints still readable, new steps use sentinel
items: Annotated[list, DeltaChannel(_messages_delta_reducer)]
```

### Async write-ordering safety

In `durability="async"` mode (default), `put_writes` calls are
fire-and-forget. `AsyncPregelLoop` tracks in-flight `aput_writes`
futures for DeltaChannel channels in `_delta_write_futs` and drains them
via `await asyncio.gather()` in `_checkpointer_put_after_previous`
before `aput()` — ensuring `checkpoint_writes` are durable before the
sentinel blob is committed.

---

## What's in scope

- **`libs/langgraph/langgraph/channels/delta.py`** — `DeltaChannel`
implementation
- **`libs/langgraph/langgraph/graph/message.py`** —
`_messages_delta_reducer` (experimental)
- **`libs/checkpoint/`** — `_get_channel_writes_history` ancestor-walk
API on `BaseCheckpointSaver`, `InMemorySaver` optimized override
- **`libs/checkpoint-postgres/`** — `PostgresSaver` /
`AsyncPostgresSaver` single-roundtrip UNION ALL override
- **`libs/langgraph/langgraph/pregel/`** — `channels_from_checkpoint` /
`create_checkpoint` wiring, async write-ordering safety

---

## Follow-ups

- **Batch reconstruction**: each DeltaChannel field issues its own
`_get_channel_writes_history` call; a single walk collecting all
sentinel channels would reduce roundtrips proportionally to the number
of DeltaChannel fields.
- **Sync write ordering**: `BackgroundExecutor.__exit__` guarantees
completion before `invoke()` returns, but within a run there's no
explicit ordering between `put_writes` and `put`. Two-phase commit for
sync would close this gap.
- **`ShallowPostgresSaver` compatibility**: shallow savers keep only the
latest checkpoint and have no parent chain to walk; DeltaChannel is
currently incompatible and should raise or warn at compile time.
- Updating the writes table w/ delta epoch ids for more efficient reads
- follow up w/ LSD checkpointer implementations to support delta
channel! and update prune

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: ccurme <chester.curme@gmail.com>
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-29 17:26:17 -04:00
a48a045596 chore: dynamic push-task timeouts (#7646)
You can Send(..., timeout=...) now. Much fun.


This would allow us to do something like adding support for an
annotation to let the LLM to pick a timeout for a given tool call, etc.

---------

Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-29 12:37:19 -07:00
open-swe[bot]GitHubopen-swe[bot] <open-swe@users.noreply.github.com>Mason DaughertyMason Daugherty
168674dd2a chore: update x links to langchain_oss (#7645)
## Description
Updates the X/Twitter social links to point to the new `@langchain_oss`
account across README badges and Python package metadata.

## Test Plan
- [ ] Verify README badges and package metadata point to
`@langchain_oss` on X

_Opened collaboratively by Mason Daugherty and open-swe._

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
2026-04-29 14:02:51 -04:00
William FHGitHubWill Fu-HinthornClaude Opus 4.7copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
800071d0d4 chore: idle timeout (#7631)
## Summary

Adds per-node `timeout` support to async StateGraph/Pregel nodes and to
the functional API (`@task` / `@entrypoint`). A timeout caps how long a
single node attempt may run, either as a hard wall-clock budget
(`run_timeout`), or as an idle window that resets on observable progress
(`idle_timeout`), or both. When exceeded, LangGraph raises
`NodeTimeoutError`, clears writes from the failed attempt, and lets the
existing retry policy decide whether to retry.

## Public API

A single `timeout=` kwarg on `add_node`, `@task`, `@entrypoint`, and
`NodeBuilder.set_timeout`. Pass a number/`timedelta` for the simple case
(treated as a hard wall-clock cap), or a `TimeoutPolicy` (in
`langgraph.types`) for finer control:

```python
from datetime import timedelta
from langgraph.types import TimeoutPolicy

# simple: hard wall-clock cap on each attempt
builder.add_node("call_model", call_model, timeout=60)
builder.add_node("call_model", call_model, timeout=timedelta(minutes=2))

# full control
builder.add_node(
    "call_model",
    call_model,
    timeout=TimeoutPolicy(
        run_timeout=120,                # hard wall-clock cap in seconds, never refreshed
        idle_timeout=30,                # cap on time without observable progress, units in seconds
        refresh_on="auto",              # "auto" | "heartbeat"
    ),
)
```

- `run_timeout`: hard wall-clock cap on a single attempt; never
refreshed.
- `idle_timeout`: progress-resetting cap. Refreshed by writes, stream
output, yielded async stream chunks, child-task scheduling, runtime
stream-writer calls, and any LangChain callback event from descendants
of the node's run. `runtime.heartbeat()` is a manual signal for work
that doesn't naturally emit any of these.
- `refresh_on="heartbeat"` narrows the refresh source to explicit
`runtime.heartbeat()` only — useful when you want a strict idle
definition that isn't reset by chatty subordinates.

For long-running async work that doesn't naturally emit progress:

```python
async def call_model(state: State, runtime: Runtime) -> State:
    while still_working:
        ...
        runtime.heartbeat()
    return {"messages": [response]}
```

`NodeTimeoutError` subclasses `TimeoutError` and carries `node`,
`timeout`, `run_timeout`, `idle_timeout`, `elapsed`, and `kind` (`"run"`
or `"idle"`). If the node's `retry_policy` permits `TimeoutError` it'll
retry; the timer resets per attempt.

## Why async-only

Sync Python code cannot be safely cancelled in-process, so timeouts only
apply to async nodes/tasks. Sync nodes with a `timeout` are rejected at
compile time (covers direct nodes, wrapped runnables, sequences, and
`RunnableParallel` branches); `run_with_retry` rejects them at runtime
as a safety net.

## What gets cancelled and what doesn't

When a watchdog fires:

1. The attempt scope is closed under a lock so any in-flight
`CONFIG_KEY_SEND` / stream / child-task scheduling that races with the
timeout is dropped atomically.
2. Buffered `task.writes` are cleared so pre-timeout writes from the
failed attempt don't leak into the checkpoint after a retry succeeds.
3. The background `asyncio.Task` is cancelled; its eventual exception is
drained via a done-callback so asyncio doesn't log it.

Only the watchdog's own `TimeoutError` converts to `NodeTimeoutError`,
so user-raised `asyncio.TimeoutError`, built-in `TimeoutError`, and
`NodeTimeoutError` from a child node continue to propagate unchanged.

Child tasks already scheduled before the timeout fired still complete —
they aren't part of the cancelled task's structured cancellation
surface. This is intentional and tested.

## External-watchdog hook

WARNING: THIS API IS IN ALPHA AND SUBJECT TO CHANGE.

`CONFIG_KEY_TIMED_ATTEMPT_OBSERVER` is a per-config callback that
receives lifecycle events for each timed attempt:

- `start` — fired before the proc runs, with `task_id`, `task_name`,
`attempt`, `run_id`, `thread_id`, `checkpoint_ns`, `started_at`, and the
configured `run_timeout_secs` / `idle_timeout_secs` / `refresh_on`.
- `progress` — fired on each progress signal that resets the idle clock,
rate-limited to ~4 events per `idle_timeout` window so token-rate
callbacks don't flood the observer. Carries the same context plus
`progress_at`.
- `finish` — fired with `finished_at`, `status` (`"success"`/`"error"`),
`error_type`, `error_message`. `ParentCommand` and `GraphBubbleUp` are
treated as control flow, not errors.

This lets an orchestrating process listen to `start` + rolling
`progress` to compute its own kill deadline (`progress_at +
idle_timeout_secs`) and hard-kill a worker process if the in-process
cancellation deadlocks. Observer callbacks run in whatever thread fires
them, and any exception they raise is logged and swallowed.

## Implementation Notes

`runtime.heartbeat()` updates the progress timestamp without taking the
guarded write lock. This avoids lock overhead on high-frequency
callback/token paths and accepts a small timestamp race window, which is
negligible for expected coarse idle-timeout configurations.

## Testing

Covers timeout retry behavior, user-raised timeout propagation,
stale-write suppression, stream / callback / heartbeat progress resets,
sync-node rejection, `RunnableParallel` branch validation,
`StateGraph.add_node` behavior, lower-level Pregel behavior, observer
start/progress/finish events, and functional API compatibility.

---------

Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-29 10:12:28 -07:00
Nick HollonandGitHub 3eb73e8ad2 feat(langgraph): native v2 projections for custom, updates, checkpoints, debug, tasks (#7640) 2026-04-29 09:43:49 -04:00
Nick HollonandGitHub 08666353fc fix(langgraph): decouple run.output/interrupted/interrupts from ValuesTransformer (#7639) 2026-04-29 09:08:21 -04:00
Nick HollonandGitHub 5af4c5addf refactor(langgraph,prebuilt): merge EventLog into StreamChannel with optional name (#7637) 2026-04-28 18:43:51 -04:00
Nick HollonandGitHub f4388df77f feat(langgraph): add streaming transformer infrastructure and tests (#7519) 2026-04-28 20:29:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>jkennedyvzClaude Opus 4.7
521b4842d3 chore(deps): bump the minor-and-patch group across 1 directory with 4 updates (ty held back) (#7635)
Bumps the minor-and-patch group with 5 updates in the /libs/sdk-py
directory:

| Package | From | To |
| --- | --- | --- |
| [orjson](https://github.com/ijl/orjson) | `3.11.7` | `3.11.8` |
| [ruff](https://github.com/astral-sh/ruff) | `0.15.6` | `0.15.12` |
| [mypy](https://github.com/python/mypy) | `1.19.1` | `1.20.2` |
| [ty](https://github.com/astral-sh/ty) | `0.0.23` | `0.0.32` |
| [pydantic](https://github.com/pydantic/pydantic) | `2.12.5` | `2.13.3`
|


Updates `orjson` from 3.11.7 to 3.11.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ijl/orjson/releases">orjson's
releases</a>.</em></p>
<blockquote>
<h2>3.11.8</h2>
<h3>Changed</h3>
<ul>
<li>Build and compatibility improvements.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ijl/orjson/blob/master/CHANGELOG.md">orjson's
changelog</a>.</em></p>
<blockquote>
<h2>3.11.8 - 2026-03-31</h2>
<h3>Changed</h3>
<ul>
<li>Build and compatibility improvements.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ijl/orjson/commit/5cbb3d0398a2f42de51210270286fecd798c5d78"><code>5cbb3d0</code></a>
3.11.8</li>
<li><a
href="https://github.com/ijl/orjson/commit/4195d7f263e33076295b75efdcbaf6a55af8674e"><code>4195d7f</code></a>
writer::half</li>
<li><a
href="https://github.com/ijl/orjson/commit/d00641b69410728a735f0855eb1c2843b0a5819b"><code>d00641b</code></a>
writer::uuid</li>
<li><a
href="https://github.com/ijl/orjson/commit/c84d9b4ba4853781af943fa5c493e261e2f82b84"><code>c84d9b4</code></a>
build and compatibility misc</li>
<li><a
href="https://github.com/ijl/orjson/commit/4547234b681fac5e0e0734cf44c21e75f9654e43"><code>4547234</code></a>
ffi::numpy</li>
<li><a
href="https://github.com/ijl/orjson/commit/0d4a5ad1f17a72528ba027554466fdec6580cdeb"><code>0d4a5ad</code></a>
datetime PyRef idiom</li>
<li><a
href="https://github.com/ijl/orjson/commit/e93a13d372ec956d027e71d023eb534b8445ac85"><code>e93a13d</code></a>
Cross-compile avoids maturin v1.12 build-details.json error</li>
<li>See full diff in <a
href="https://github.com/ijl/orjson/compare/3.11.7...3.11.8">compare
view</a></li>
</ul>
</details>
<br />

Updates `ruff` from 0.15.6 to 0.15.12
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/releases">ruff's
releases</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>Install ruff 0.15.12</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/ruff/releases/download/0.15.12/ruff-installer.sh
| sh
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's
changelog</a>.</em></p>
<blockquote>
<h2>0.15.12</h2>
<p>Released on 2026-04-24.</p>
<h3>Preview features</h3>
<ul>
<li>Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23599">#23599</a>)</li>
<li>Implement <code>#ruff:ignore</code> logical-line suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23404">#23404</a>)</li>
<li>Revert preview changes to displayed diagnostic severity in LSP (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24789">#24789</a>)</li>
<li>[<code>airflow</code>] Implement
<code>task-branch-as-short-circuit</code> (<code>AIR004</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23579">#23579</a>)</li>
<li>[<code>flake8-bugbear</code>] Fix
<code>break</code>/<code>continue</code> handling in
<code>loop-iterator-mutation</code> (<code>B909</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24440">#24440</a>)</li>
<li>[<code>pylint</code>] Fix <code>PLC2701</code> for type parameter
scopes (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24576">#24576</a>)</li>
</ul>
<h3>Rule changes</h3>
<ul>
<li>[<code>pandas-vet</code>] Suggest <code>.array</code> as well in
<code>PD011</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24805">#24805</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Respect default Unix permissions for cache files (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24794">#24794</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pylint</code>] Fix <code>PLR0124</code> description not to
claim self-comparison always returns the same value (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24749">#24749</a>)</li>
<li>[<code>pyupgrade</code>] Expand docs on reusable
<code>TypeVar</code>s and scoping (<code>UP046</code>) (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24153">#24153</a>)</li>
<li>Improve rules table accessibility (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24711">#24711</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a
href="https://github.com/woodruffw"><code>@​woodruffw</code></a></li>
<li><a
href="https://github.com/avasis-ai"><code>@​avasis-ai</code></a></li>
<li><a href="https://github.com/Dev-iL"><code>@​Dev-iL</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a
href="https://github.com/ShipItAndPray"><code>@​ShipItAndPray</code></a></li>
<li><a
href="https://github.com/anishgirianish"><code>@​anishgirianish</code></a></li>
<li><a
href="https://github.com/augustelalande"><code>@​augustelalande</code></a></li>
<li><a
href="https://github.com/amyreese"><code>@​amyreese</code></a></li>
<li><a
href="https://github.com/majiayu000"><code>@​majiayu000</code></a></li>
</ul>
<h2>0.15.11</h2>
<p>Released on 2026-04-16.</p>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Ignore <code>RUF029</code> when function is
decorated with <code>asynccontextmanager</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24642">#24642</a>)</li>
<li>[<code>airflow</code>] Implement
<code>airflow-xcom-pull-in-template-string</code> (<code>AIR201</code>)
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/23583">#23583</a>)</li>
<li>[<code>flake8-bandit</code>] Fix <code>S103</code> false positives
and negatives in mask analysis (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24424">#24424</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ruff/commit/66f93cf7ed4d36325f35a452e4afa28268fbcd28"><code>66f93cf</code></a>
Bump 0.15.12 (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24815">#24815</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/476a4d02e8e3b6c157ac39979d8b698a1b6baa91"><code>476a4d0</code></a>
[ty] Complete support for more detailed diagnostics on possibly unbound
error...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/ed669eab30095d6c51fe6cdef6050fb01276bcb3"><code>ed669ea</code></a>
Implement <code>#ruff:file-ignore</code> file-level suppressions (<a
href="https://redirect.github.com/astral-sh/ruff/issues/23599">#23599</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e73d952e43feb51356ee740c5a973fce81396ff6"><code>e73d952</code></a>
[ty] Include inferred type in <code>invalid-key</code> concise
diagnostic for union/inte...</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/80feb29b31cd98c093316df2e0407b0c70c01b55"><code>80feb29</code></a>
[ty] report only dead annotation-only locals as unused (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24811">#24811</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/0fbf2bc27336a3d17d39af52cf89b78dcda8c7c8"><code>0fbf2bc</code></a>
Drop deprecated license classifier (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24808">#24808</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/43b174cc7f2fcb0080bb1d4843cd4bf6b72bbe27"><code>43b174c</code></a>
[ty] Infer lambda parameter types with <code>Callable</code> type
context (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24317">#24317</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/4f449ae4a2377569330a5ab94799d389357b5a3f"><code>4f449ae</code></a>
[ty] Add error context for intersection types (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24772">#24772</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/5b4e753acb46e96ad408e4904c15308e33efe307"><code>5b4e753</code></a>
[ty] Add support for goto in literal enum member inlay hint (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24792">#24792</a>)</li>
<li><a
href="https://github.com/astral-sh/ruff/commit/e7cc76275a758ce1c636ea1c2d091fd576aac794"><code>e7cc762</code></a>
[ty] Add error context for TypedDict assignments (<a
href="https://redirect.github.com/astral-sh/ruff/issues/24790">#24790</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ruff/compare/0.15.6...0.15.12">compare
view</a></li>
</ul>
</details>
<br />

Updates `mypy` from 1.19.1 to 1.20.2
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's
changelog</a>.</em></p>
<blockquote>
<h3>Mypy 1.20.2</h3>
<ul>
<li>Use WAL with SQLite cache and fix close (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21154">21154</a>)</li>
<li>Adjust SQLite journal mode (Ivan Levkivskyi, PR <a
href="https://redirect.github.com/python/mypy/pull/21217">21217</a>)</li>
<li>Correctly aggregate narrowing information on parent expressions
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21206">21206</a>)</li>
<li>Fix regression related to generic callables (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21208">21208</a>)</li>
<li>Fix regression by avoiding widening types in some contexts
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21242">21242</a>)</li>
<li>Fix slicing in non-strict optional mode (Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21282">21282</a>)</li>
<li>mypyc: Fix match statement semantics for &quot;or&quot; pattern
(Shantanu, PR <a
href="https://redirect.github.com/python/mypy/pull/21156">21156</a>)</li>
<li>mypyc: Fix issue with module dunder attributes (Piotr Sawicki, PR <a
href="https://redirect.github.com/python/mypy/pull/21275">21275</a>)</li>
<li>Initial support for Python 3.15.0a8 (Marc Mueller, PR <a
href="https://redirect.github.com/python/mypy/pull/21255">21255</a>)</li>
</ul>
<h3>Acknowledgements</h3>
<p>Thanks to all mypy contributors who contributed to this release:</p>
<ul>
<li>A5rocks</li>
<li>Aaron Wieczorek</li>
<li>Adam Turner</li>
<li>Ali Hamdan</li>
<li>asce</li>
<li>BobTheBuidler</li>
<li>Brent Westbrook</li>
<li>Brian Schubert</li>
<li>bzoracler</li>
<li>Chris Burroughs</li>
<li>Christoph Tyralla</li>
<li>Colin Watson</li>
<li>Donghoon Nam</li>
<li>E. M. Bray</li>
<li>Emma Smith</li>
<li>Ethan Sarp</li>
<li>George Ogden</li>
<li>getzze</li>
<li>grayjk</li>
<li>Gregor Riepl</li>
<li>Ivan Levkivskyi</li>
<li>James Hilliard</li>
<li>James Le Cuirot</li>
<li>Jeremy Nimmer</li>
<li>Joren Hammudoglu</li>
<li>Kai (Kazuya Ito)</li>
<li>kaushal trivedi</li>
<li>Kevin Kannammalil</li>
<li>Lukas Geiger</li>
<li>Łukasz Langa</li>
<li>Marc Mueller</li>
<li>Michael R. Crusoe</li>
<li>michaelm-openai</li>
<li>Neil Schemenauer</li>
<li>Piotr Sawicki</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/python/mypy/commit/145a062651b5f9996b75ef32b7040bd2e885ed82"><code>145a062</code></a>
Bump version to 1.20.2</li>
<li><a
href="https://github.com/python/mypy/commit/81cd49215c288eacb987de066f02daff2553b7c7"><code>81cd492</code></a>
Fix slicing with nonstrict optional (<a
href="https://redirect.github.com/python/mypy/issues/21282">#21282</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/908d3441eecbaa2a6193165317177db834d7ca1a"><code>908d344</code></a>
[mypyc] Set dunder attrs when adding module to sys.modules (<a
href="https://redirect.github.com/python/mypy/issues/21275">#21275</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/ba28610fac9d2b33be210ca8dcfe4bc47b7af424"><code>ba28610</code></a>
Initial support for Python 3.15.0a8 (<a
href="https://redirect.github.com/python/mypy/issues/21255">#21255</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/7b0e09f48dbd3717ed008a273cd17e8e960c2037"><code>7b0e09f</code></a>
Fix match statement semantics for &quot;or&quot; pattern (<a
href="https://redirect.github.com/python/mypy/issues/21156">#21156</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/92b74f226de62f7505f5ef5cb158e8ec9c58b8b7"><code>92b74f2</code></a>
Avoid widening types in conditional_types (<a
href="https://redirect.github.com/python/mypy/issues/21242">#21242</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/0dcbfaa40b0e360a16baea9cf851955375d91b54"><code>0dcbfaa</code></a>
Fix is_overlapping_types for generic callables (<a
href="https://redirect.github.com/python/mypy/issues/21208">#21208</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/210f518dede35292033ef0d387847406a0ccef8f"><code>210f518</code></a>
Correctly aggregate narrowing information on parent expressions (<a
href="https://redirect.github.com/python/mypy/issues/21206">#21206</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/c34530e53a10e385d8b0f1af4baa88a596b5ceaa"><code>c34530e</code></a>
Only set journal mode in coordinator (<a
href="https://redirect.github.com/python/mypy/issues/21217">#21217</a>)</li>
<li><a
href="https://github.com/python/mypy/commit/79a3ec6d01b56a27c00e9b3320c2b1d4d73a77f9"><code>79a3ec6</code></a>
Use WAL with SQLite cache, fix close (<a
href="https://redirect.github.com/python/mypy/issues/21154">#21154</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/python/mypy/compare/v1.19.1...v1.20.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `ty` from 0.0.23 to 0.0.32
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/releases">ty's
releases</a>.</em></p>
<blockquote>
<h2>0.0.32</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-20.</p>
<h3>Bug fixes</h3>
<ul>
<li>Fix panic when <code>__get__</code> uses <code>Concatenate</code>
<code>self</code>-type and wraps a <code>__call__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24692">#24692</a>)</li>
<li>Avoid panicking on overloaded <code>Callable</code> type context (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24661">#24661</a>)</li>
<li>Expand class bases in per-base lint checks (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24695">#24695</a>,
<a
href="https://redirect.github.com/astral-sh/ruff/pull/24699">#24699</a>)</li>
<li>Fix stack overflow for binary operator inference involving recursive
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24551">#24551</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Dim out unreachable code in IDEs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24580">#24580</a>)</li>
<li>Do not suggest argument completion when typing the value of a
keyword argument (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24669">#24669</a>)</li>
<li>Retrieve the docstring from the overload implementation if an
<code>@overload</code>-decorated function has no docstring (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23920">#23920</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Allow <code>if</code> statements in TypedDict bodies (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24702">#24702</a>)</li>
<li>Disallow <code>@disjoint_base</code> on TypedDicts and Protocols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24671">#24671</a>)</li>
<li>Do not consider a subclass of a
<code>@dataclass_transform</code>-decorated class to have dataclass-like
semantics if it has <code>type</code> in its MRO (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24679">#24679</a>)</li>
<li>Reject using properties with <code>Never</code> setters or deleters
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24510">#24510</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24646">#24646</a>).
<a
href="https://github.com/python/typeshed/compare/f8f0794d0fe249c06dc9f31a004d85be6cca6ced...c03c2b926422c82ab680d27f3ad2491845000802">Typeshed
diff</a></li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Show error context for assignability diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24309">#24309</a>)</li>
<li>Use partially qualified names when reporting diagnostics regarding
bad calls to methods (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24560">#24560</a>)</li>
<li>Reduce source code context window to zero (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24689">#24689</a>)</li>
<li>Merge same-file annotations if there is only a single line
separating them (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24694">#24694</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Memoize binary operator return types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24700">#24700</a>)</li>
<li>Gate protocol compatibility on member count (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24684">#24684</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/JelleZijlstra"><code>@​JelleZijlstra</code></a></li>
<li><a href="https://github.com/kc0506"><code>@​kc0506</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/dcreager"><code>@​dcreager</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a
href="https://github.com/ibraheemdev"><code>@​ibraheemdev</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/ty/blob/main/CHANGELOG.md">ty's
changelog</a>.</em></p>
<blockquote>
<h2>0.0.32</h2>
<p>Released on 2026-04-20.</p>
<h3>Bug fixes</h3>
<ul>
<li>Fix panic when <code>__get__</code> uses <code>Concatenate</code>
<code>self</code>-type and wraps a <code>__call__</code> (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24692">#24692</a>)</li>
<li>Avoid panicking on overloaded <code>Callable</code> type context (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24661">#24661</a>)</li>
<li>Expand class bases in per-base lint checks (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24695">#24695</a>,
<a
href="https://redirect.github.com/astral-sh/ruff/pull/24699">#24699</a>)</li>
<li>Fix stack overflow for binary operator inference involving recursive
types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24551">#24551</a>)</li>
</ul>
<h3>LSP server</h3>
<ul>
<li>Dim out unreachable code in IDEs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24580">#24580</a>)</li>
<li>Do not suggest argument completion when typing the value of a
keyword argument (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24669">#24669</a>)</li>
<li>Retrieve the docstring from the overload implementation if an
<code>@overload</code>-decorated function has no docstring (<a
href="https://redirect.github.com/astral-sh/ruff/pull/23920">#23920</a>)</li>
</ul>
<h3>Core type checking</h3>
<ul>
<li>Allow <code>if</code> statements in TypedDict bodies (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24702">#24702</a>)</li>
<li>Disallow <code>@disjoint_base</code> on TypedDicts and Protocols (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24671">#24671</a>)</li>
<li>Do not consider a subclass of a
<code>@dataclass_transform</code>-decorated class to have dataclass-like
semantics if it has <code>type</code> in its MRO (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24679">#24679</a>)</li>
<li>Reject using properties with <code>Never</code> setters or deleters
(<a
href="https://redirect.github.com/astral-sh/ruff/pull/24510">#24510</a>)</li>
<li>Sync vendored typeshed stubs (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24646">#24646</a>).
<a
href="https://github.com/python/typeshed/compare/f8f0794d0fe249c06dc9f31a004d85be6cca6ced...c03c2b926422c82ab680d27f3ad2491845000802">Typeshed
diff</a></li>
</ul>
<h3>Diagnostics</h3>
<ul>
<li>Show error context for assignability diagnostics (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24309">#24309</a>)</li>
<li>Use partially qualified names when reporting diagnostics regarding
bad calls to methods (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24560">#24560</a>)</li>
<li>Reduce source code context window to zero (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24689">#24689</a>)</li>
<li>Merge same-file annotations if there is only a single line
separating them (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24694">#24694</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Memoize binary operator return types (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24700">#24700</a>)</li>
<li>Gate protocol compatibility on member count (<a
href="https://redirect.github.com/astral-sh/ruff/pull/24684">#24684</a>)</li>
</ul>
<h3>Contributors</h3>
<ul>
<li><a
href="https://github.com/JelleZijlstra"><code>@​JelleZijlstra</code></a></li>
<li><a href="https://github.com/kc0506"><code>@​kc0506</code></a></li>
<li><a
href="https://github.com/denyszhak"><code>@​denyszhak</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a
href="https://github.com/dcreager"><code>@​dcreager</code></a></li>
<li><a
href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a href="https://github.com/dylwil3"><code>@​dylwil3</code></a></li>
<li><a
href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/sharkdp"><code>@​sharkdp</code></a></li>
<li><a
href="https://github.com/ibraheemdev"><code>@​ibraheemdev</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/ty/commit/4d1e1fc57ca8bfdcbcee513ba92135d2932eb279"><code>4d1e1fc</code></a>
Bump version to 0.0.32 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3302">#3302</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/a537bdefc97080d4400113eb6635ae133c5b3bcf"><code>a537bde</code></a>
Update PyO3/maturin-action action to v1.51.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3300">#3300</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/81e41250e0b53893f1f86eae3bc59867f15720ac"><code>81e4125</code></a>
Update actions/upload-artifact action to v7.0.1 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3296">#3296</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/a9dd1cbb536838e21b36b2c459b5bf6098c295cf"><code>a9dd1cb</code></a>
Update docker/build-push-action action to v7.1.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3299">#3299</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/ef0a7dd496dbf60000d79ac92ff171191cae424b"><code>ef0a7dd</code></a>
Update actions/github-script action to v9 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3301">#3301</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/74f058324890813da652964cdf2d29c1207fdeab"><code>74f0583</code></a>
Update astral-sh/setup-uv action to v8.1.0 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3298">#3298</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/82799cc0d01beed42e06c110bdaabb9ab243fce2"><code>82799cc</code></a>
Update prek dependencies (<a
href="https://redirect.github.com/astral-sh/ty/issues/3297">#3297</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/daaa40454966558ffd2a0ea3dcf86bc1749c8715"><code>daaa404</code></a>
Bump version to 0.0.31 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3280">#3280</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/12e86b58b5034a29266268195afc16feeab6e3ea"><code>12e86b5</code></a>
Bump version to 0.0.30 (<a
href="https://redirect.github.com/astral-sh/ty/issues/3270">#3270</a>)</li>
<li><a
href="https://github.com/astral-sh/ty/commit/67077ad5fbde5adc1df0e3169525040ad1139dab"><code>67077ad</code></a>
Reorder sections in FAQ (<a
href="https://redirect.github.com/astral-sh/ty/issues/3267">#3267</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/ty/compare/0.0.23...0.0.32">compare
view</a></li>
</ul>
</details>
<br />

Updates `pydantic` from 2.12.5 to 2.13.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pydantic/pydantic/releases">pydantic's
releases</a>.</em></p>
<blockquote>
<h2>v2.13.3 2026-04-20</h2>
<h2>v2.13.3 (2026-04-20)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13096">#13096</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.2...v2.13.3">https://github.com/pydantic/pydantic/compare/v2.13.2...v2.13.3</a></p>
<h2>v2.13.2 2026-04-17</h2>
<h2>v2.13.2 (2026-04-17)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13084">#13084</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.1...v2.13.2">https://github.com/pydantic/pydantic/compare/v2.13.1...v2.13.2</a></p>
<h2>v2.13.1 2026-04-15</h2>
<h2>v2.13.1 (2026-04-15)</h2>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/davidhewitt"><code>@​davidhewitt</code></a> in
<a
href="https://redirect.github.com/pydantic/pydantic/pull/13079">#13079</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/pydantic/pydantic/compare/v2.13.0...v2.13.1">https://github.com/pydantic/pydantic/compare/v2.13.0...v2.13.1</a></p>
<h2>v2.13.0 2026-04-13</h2>
<h2>v2.13.0 (2026-04-13)</h2>
<p>The highlights of the v2.13 release are available in the <a
href="https://pydantic.dev/articles/pydantic-v2-13-release">blog
post</a>.
Several minor changes (considered non-breaking changes according to our
<a
href="https://pydantic.dev/docs/validation/2.13/get-started/version-policy/#pydantic-v2">versioning
policy</a>) are also included in this release. Make sure to look into
them before upgrading.</p>
<p>This release contains the updated <code>pydantic.v1</code> namespace,
matching version 1.10.26 which includes support for Python 3.14.</p>
<h3>What's Changed</h3>
<p>See the beta releases for all changes sinces 2.12.</p>
<h4>Packaging</h4>
<ul>
<li>Add zizmor for GitHub Actions workflow linting by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13039">#13039</a></li>
<li>Update jiter to v0.14.0 to fix a segmentation fault on musl Linux by
<a href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13064">#13064</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pydantic/pydantic/blob/main/HISTORY.md">pydantic's
changelog</a>.</em></p>
<blockquote>
<h2>v2.13.3 (2026-04-20)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.3">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13096">#13096</a></li>
</ul>
<h2>v2.13.2 (2026-04-17)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.2">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13084">#13084</a></li>
</ul>
<h2>v2.13.1 (2026-04-15)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.1">GitHub
release</a></p>
<h3>What's Changed</h3>
<h4>Fixes</h4>
<ul>
<li>Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code> by <a
href="https://github.com/davidhewitt"><code>@​davidhewitt</code></a> in
<a
href="https://redirect.github.com/pydantic/pydantic/pull/13079">#13079</a></li>
</ul>
<h2>v2.13.0 (2026-04-13)</h2>
<p><a
href="https://github.com/pydantic/pydantic/releases/tag/v2.13.0">GitHub
release</a></p>
<p>The highlights of the v2.13 release are available in the <a
href="https://pydantic.dev/articles/pydantic-v2-13-release">blog
post</a>.
Several minor changes (considered non-breaking changes according to our
<a
href="https://pydantic.dev/docs/validation/2.13/get-started/version-policy/#pydantic-v2">versioning
policy</a>)
are also included in this release. Make sure to look into them before
upgrading.</p>
<p>This release contains the updated <code>pydantic.v1</code> namespace,
matching version 1.10.26 which includes support for Python 3.14.</p>
<h3>What's Changed</h3>
<p>See the beta releases for all changes sinces 2.12.</p>
<h4>New Features</h4>
<ul>
<li>Allow default factories of private attributes to take validated
model data by <a
href="https://github.com/Viicos"><code>@​Viicos</code></a> in <a
href="https://redirect.github.com/pydantic/pydantic/pull/13013">#13013</a></li>
</ul>
<h4>Changes</h4>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pydantic/pydantic/commit/9e9a11132c980c4ec84ff6d2df06d4c1a55ca8f3"><code>9e9a111</code></a>
Fix backported test</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/1ec8c6aa0cae1c78409c6f31af738a24e16c7124"><code>1ec8c6a</code></a>
Prepare release v2.13.3</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/fb4f2044436e9fa4ff9745479ce263016a99c4d6"><code>fb4f204</code></a>
Handle <code>AttributeError</code> subclasses with
<code>from_attributes</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/ca3ddd1149bd3b14273922a29da174804496a055"><code>ca3ddd1</code></a>
Prepare release v2.13.2</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/000e823a3ab7545b0c6064fde34effdcd02044ab"><code>000e823</code></a>
Fix <code>ValidationInfo.field_name</code> missing with
<code>model_validate_json()</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/d45d8be3ffb6dfc6d0c64da6980140c91ff841fb"><code>d45d8be</code></a>
Prepare release 2.13.1</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/54aca609ce58e73dfe1c681d93432fbe3a4621da"><code>54aca60</code></a>
Fix <code>ValidationInfo.data</code> missing with
<code>model_validate_json()</code></li>
<li><a
href="https://github.com/pydantic/pydantic/commit/46bf4fa648af3a1fbf4603a37f210e9d9c618357"><code>46bf4fa</code></a>
Fix Pydantic release workflow (<a
href="https://redirect.github.com/pydantic/pydantic/issues/13067">#13067</a>)</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/1b359edab09c623464d23c6fd2503ae5ff276d43"><code>1b359ed</code></a>
Prepare release v2.13.0 (<a
href="https://redirect.github.com/pydantic/pydantic/issues/13065">#13065</a>)</li>
<li><a
href="https://github.com/pydantic/pydantic/commit/b1bf19445d8ac144a7a0e82674d2d87eebab6c18"><code>b1bf194</code></a>
Fix model equality when using runtime <code>extra</code> configuration
(<a
href="https://redirect.github.com/pydantic/pydantic/issues/13062">#13062</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pydantic/pydantic/compare/v2.12.5...v2.13.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: jkennedyvz <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:20:26 -07:00
Sydney RunkleandGitHub cb328b57f1 release(prebuilt): 1.0.12, langgraph 1.1.10 (#7623)
## Summary

- Bumps `langgraph-prebuilt` `1.0.11` → `1.0.12`
- Bumps `langgraph` `1.1.9` → `1.1.10` (requires
`langgraph-prebuilt>=1.0.12`)
- Updates all downstream `uv.lock` files

## Changes since last release

**prebuilt (`1.0.11` → `1.0.12`)**
- fix(prebuilt): hydrate ToolNode state from channels via pregel helpers
(#7594)

**langgraph (`1.1.8` → `1.1.10`)**
- fix: don't propagate ReplayState to subgraphs on plain resume (#7561)
2026-04-27 13:11:43 -04:00
William FHandGitHub d177a0db43 Revert "chore: node-level timeouts" (#7627)
Reverts langchain-ai/langgraph#7599

I am going to implement this as an `idle_timeout` instead. I think
that's a better default behavior.
2026-04-27 09:03:19 -07:00
Sydney RunkleandGitHub 372d54dc4f release(checkpoint): 4.0.3 (#7625)
## Summary

Bumps `langgraph-checkpoint` `4.0.2` → `4.0.3` and updates all
downstream `uv.lock` files.

## Changes since 4.0.2

- fix(checkpoint): revive lc=2 JSON blobs for safe types without
allowlist (#7582)
- chore: dedup warnings (#7257)
- chore(deps): bump langsmith from 0.6.4 to 0.7.31 (#7525)
2026-04-27 10:27:56 -04:00
f4aee546ad fix(prebuilt): hydrate ToolNode state from channels via pregel helpers (#7594)
## Summary

When `ToolNode` receives a bare `[tool_call]` list via the Send API (the
dispatch shape `create_agent` will use once langchain-ai/langchain#36960
lands), hydrate `ToolRuntime.state` from the current channel values
instead of requiring the dispatcher to inline the full agent state dict
into every `Send.arg`.

Motivation: the paired langchain PR drops the `ToolCallWithContext`
wrapper from `create_agent`'s tool dispatch, which eliminates an O(N²)
storage term on `__pregel_tasks` checkpoint writes. Without this
companion change there would be no path for the tool node to see the
graph state.

## What changed

- `libs/prebuilt/langgraph/prebuilt/tool_node.py` — `_extract_state`
grows a third branch for list-form input. When the input is a list whose
last entry is a `ToolCall` dict, read the current channel values via
`CONFIG_KEY_READ` and return them as the state dict.

The full new logic is four lines inline in `_extract_state`:

```python
read = config.get(CONF, {}).get(CONFIG_KEY_READ)
if read is None:
    return {}
# Pregel installs CONFIG_KEY_READ as
# `functools.partial(local_read, scratchpad, channels, managed, task)`.
channels = read.args[1]
return cast("dict[str, Any]", read(list(channels), False))
```

- No changes to the pregel read machinery (`local_read`, `ChannelRead`).
- Only channel values are read; managed values have their own injection
path (`ToolRuntime.context`, `InjectedContext`) and were never in the
pre-fix inlined state dict, so we don't add them here.
- Falls back to `{}` when invoked outside a Pregel context (e.g. direct
`ToolNode(...).invoke([tool_call])` from a test harness), which
preserves existing `ToolNode` direct-invocation test behavior.

- `libs/prebuilt/tests/test_on_tool_call.py` — two new tests covering
the list-form hydration path (sync + async). They build a
`functools.partial` that matches Pregel's real `CONFIG_KEY_READ` shape
and assert `ToolRuntime.state` reflects the current channel values.

## Why it's safe

- **Same snapshot semantics as before.** `Send` is emitted at
end-of-super-step-N; consumed at start-of-super-step-N+1. Channels at
that point reflect every write from super-step N (including the new
AIMessage the tool calls originated from). Parallel tool tasks in the
tools super-step all read the same values since sibling writes don't
land until end-of-super-step.
- **Legacy `ToolCallWithContext` path preserved.** External dispatchers
that still inline state continue to work unchanged — `_extract_state`
checks that branch first.

## Test plan

- [x] `make test` in `libs/prebuilt` — **204 pass**
- [x] Two new hydration tests (sync + async) green
- [x] `make format` / `make lint` / `mypy` clean

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:52:23 -04:00
85cd64ed69 fix(checkpoint): revive lc=2 JSON blobs for safe types without allowlist (#7582)
## Summary

Fixes #7498 — `MESSAGE_COERCION_FAILURE` when resuming threads
checkpointed before v1.0.1.

**Root cause:** PR #6269 (v1.0.1) added an `_allowed_json_modules`
security gate to `JsonPlusSerializer._reviver`. The gate defaults to
`None`, so old `"json"`-format checkpoint blobs containing `lc=2`
constructor dicts (the pre-msgpack serialization format for pydantic
objects like `HumanMessage`) are now returned as raw dicts instead of
being reconstructed. Those raw dicts reach `add_messages →
convert_to_messages`, which sees `type="constructor"` and raises
`MESSAGE_COERCION_FAILURE`. Fresh first-turn messages are unaffected
because current `dumps_typed` only writes `"msgpack"` blobs.

**Fix:** `_reviver` now reconstructs `lc=2` blobs whose target class is
already in `SAFE_MSGPACK_TYPES` — the same curated allowlist already
used by the msgpack deserialization path (includes all standard
LangChain message types). Unknown classes are still blocked, preserving
the security intent of #6269.

## Changes

- `libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py` — add
`_is_safe_json_type()` helper; update `_reviver` and
`_check_allowed_json_modules` to allow safe types without an explicit
allowlist
- `libs/checkpoint/tests/test_jsonplus.py` — two new regression tests:
safe-type `lc=2` blobs revive correctly; unknown-type `lc=2` blobs stay
blocked

## Test plan

- [ ] `test_lc2_json_safe_type_revives_without_allowlist` —
`HumanMessage`/`AIMessage` lc=2 JSON blobs round-trip to proper
`BaseMessage` objects with no allowlist configured
- [ ] `test_lc2_json_unknown_type_stays_blocked_without_allowlist` —
`pprint.pprint` lc=2 blob still returns raw dict (not reconstructed)
- [ ] `test_deserde_invalid_module` — existing behaviour unchanged
- [ ] Full `test_jsonplus.py` suite: 93/93 passing

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 09:08:47 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
53a9806e65 chore(deps): bump nbconvert from 7.17.0 to 7.17.1 in /libs/langgraph (#7573)
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.17.0 to
7.17.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jupyter/nbconvert/releases">nbconvert's
releases</a>.</em></p>
<blockquote>
<h2>v7.17.1</h2>
<h2>7.17.1</h2>
<p>This is a security release, fixing two CVEs:</p>
<ul>
<li><a
href="https://github.com/jupyter/nbconvert/security/advisories/GHSA-4c99-qj7h-p3vg">CVE-2026-39377</a></li>
<li><a
href="https://github.com/jupyter/nbconvert/security/advisories/GHSA-7jqv-fw35-gmx9">CVE-2026-39378</a></li>
</ul>
<p>(full advisories will be published seven days after release, on
2026-04-14).</p>
<p>(<a
href="https://github.com/jupyter/nbconvert/compare/v7.17.0...b3b6ec01f872e9af8fd1769eb9cf1889c720ecf3">Full
Changelog</a>)</p>
<h3>Enhancements made</h3>
<ul>
<li>Allow configureable WebPDF JavaScript processing timeout <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2250">#2250</a>
(<a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>, <a
href="https://github.com/Carreau"><code>@​Carreau</code></a>)</li>
</ul>
<h3>Bugs fixed</h3>
<ul>
<li>Fix <code>PermissionError</code> when checking template paths on
shared filesystems <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2252">#2252</a>
(<a href="https://github.com/ctcjab"><code>@​ctcjab</code></a>, <a
href="https://github.com/krassowski"><code>@​krassowski</code></a>)</li>
<li>Tweak webpdf template logic to fix duplicate extension problem <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2249">#2249</a>
(<a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>, <a
href="https://github.com/Carreau"><code>@​Carreau</code></a>)</li>
</ul>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>specify python version for pre <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2276">#2276</a>
(<a href="https://github.com/minrk"><code>@​minrk</code></a>, <a
href="https://github.com/krassowski"><code>@​krassowski</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/nbconvert/graphs/contributors?from=2026-01-29&amp;to=2026-04-08&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/akhmerov"><code>@​akhmerov</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Aakhmerov+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/bollwyvl"><code>@​bollwyvl</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Abollwyvl+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/Carreau"><code>@​Carreau</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3ACarreau+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/ctcjab"><code>@​ctcjab</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Actcjab+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a
href="https://github.com/davidbrochart"><code>@​davidbrochart</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Adavidbrochart+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/Ken-B"><code>@​Ken-B</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3AKen-B+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/krassowski"><code>@​krassowski</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akrassowski+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/mgeier"><code>@​mgeier</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Amgeier+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/minrk"><code>@​minrk</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Aminrk+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/mpacer"><code>@​mpacer</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ampacer+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/MSeal"><code>@​MSeal</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3AMSeal+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a
href="https://github.com/SylvainCorlay"><code>@​SylvainCorlay</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3ASylvainCorlay+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/takluyver"><code>@​takluyver</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atakluyver+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atimkpaine+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md">nbconvert's
changelog</a>.</em></p>
<blockquote>
<h2>7.17.1</h2>
<p>This is a security release, fixing two CVEs:</p>
<ul>
<li><a
href="https://github.com/jupyter/nbconvert/security/advisories/GHSA-4c99-qj7h-p3vg">CVE-2026-39377</a></li>
<li><a
href="https://github.com/jupyter/nbconvert/security/advisories/GHSA-7jqv-fw35-gmx9">CVE-2026-39378</a></li>
</ul>
<p>(full advisories will be published seven days after release, on
2026-04-14).</p>
<p>(<a
href="https://github.com/jupyter/nbconvert/compare/v7.17.0...b3b6ec01f872e9af8fd1769eb9cf1889c720ecf3">Full
Changelog</a>)</p>
<h3>Enhancements made</h3>
<ul>
<li>Allow configureable WebPDF JavaScript processing timeout <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2250">#2250</a>
(<a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>, <a
href="https://github.com/Carreau"><code>@​Carreau</code></a>)</li>
</ul>
<h3>Bugs fixed</h3>
<ul>
<li>Fix <code>PermissionError</code> when checking template paths on
shared filesystems <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2252">#2252</a>
(<a href="https://github.com/ctcjab"><code>@​ctcjab</code></a>, <a
href="https://github.com/krassowski"><code>@​krassowski</code></a>)</li>
<li>Tweak webpdf template logic to fix duplicate extension problem <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2249">#2249</a>
(<a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>, <a
href="https://github.com/Carreau"><code>@​Carreau</code></a>)</li>
</ul>
<h3>Maintenance and upkeep improvements</h3>
<ul>
<li>specify python version for pre <a
href="https://redirect.github.com/jupyter/nbconvert/pull/2276">#2276</a>
(<a href="https://github.com/minrk"><code>@​minrk</code></a>, <a
href="https://github.com/krassowski"><code>@​krassowski</code></a>)</li>
</ul>
<h3>Contributors to this release</h3>
<p>The following people contributed discussions, new ideas, code and
documentation contributions, and review.
See <a
href="https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports">our
definition of contributors</a>.</p>
<p>(<a
href="https://github.com/jupyter/nbconvert/graphs/contributors?from=2026-01-29&amp;to=2026-04-08&amp;type=c">GitHub
contributors page for this release</a>)</p>
<p><a href="https://github.com/akhmerov"><code>@​akhmerov</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Aakhmerov+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/bollwyvl"><code>@​bollwyvl</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Abollwyvl+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/Carreau"><code>@​Carreau</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3ACarreau+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/ctcjab"><code>@​ctcjab</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Actcjab+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a
href="https://github.com/davidbrochart"><code>@​davidbrochart</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Adavidbrochart+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/Ken-B"><code>@​Ken-B</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3AKen-B+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/krassowski"><code>@​krassowski</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akrassowski+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/mgeier"><code>@​mgeier</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Amgeier+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/minrk"><code>@​minrk</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Aminrk+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/mpacer"><code>@​mpacer</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ampacer+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/MSeal"><code>@​MSeal</code></a> (<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3AMSeal+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a
href="https://github.com/SylvainCorlay"><code>@​SylvainCorlay</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3ASylvainCorlay+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/takluyver"><code>@​takluyver</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atakluyver+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)
| <a href="https://github.com/timkpaine"><code>@​timkpaine</code></a>
(<a
href="https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atimkpaine+updated%3A2026-01-29..2026-04-08&amp;type=Issues">activity</a>)</p>
<!-- raw HTML omitted -->
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/jupyter/nbconvert/commit/78ed30837a607deab7cf0a12dca072bf3f63417a"><code>78ed308</code></a>
Publish 7.17.1</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/f090a64606fde4c7f87cbf478f51b4aa46a425ec"><code>f090a64</code></a>
ruff format</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/b3b6ec01f872e9af8fd1769eb9cf1889c720ecf3"><code>b3b6ec0</code></a>
chore: update pre-commit hooks (<a
href="https://redirect.github.com/jupyter/nbconvert/issues/2277">#2277</a>)</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/be4841f7da51c499b1937e41e6e71926dbe0daa3"><code>be4841f</code></a>
ignore silly security lint in tests</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/26d57b295870f5572d9bd0535acee4a120339c10"><code>26d57b2</code></a>
fix type annotation on Lexer</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/0e6b8ccabf2aca6c18fac8c574f22b7155f441fb"><code>0e6b8cc</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/ba5e5cdd737704388251fa55fa9e58f5752fa39d"><code>ba5e5cd</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/1db0c88d865146ce02b6405a8d96753d3d0cd0c2"><code>1db0c88</code></a>
Specify python version for pre (<a
href="https://redirect.github.com/jupyter/nbconvert/issues/2276">#2276</a>)</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/7473fc3037a6317bff54380e3a7162d73bf089b3"><code>7473fc3</code></a>
chore: update pre-commit hooks (<a
href="https://redirect.github.com/jupyter/nbconvert/issues/2242">#2242</a>)</li>
<li><a
href="https://github.com/jupyter/nbconvert/commit/4322f7f290694929f414cefadc942111afad3762"><code>4322f7f</code></a>
Bump the actions group across 1 directory with 2 updates (<a
href="https://redirect.github.com/jupyter/nbconvert/issues/2273">#2273</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/jupyter/nbconvert/compare/v7.17.0...v7.17.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nbconvert&package-manager=uv&previous-version=7.17.0&new-version=7.17.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 20:30:47 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
219fbbe8d0 chore(deps): bump python-dotenv from 1.2.1 to 1.2.2 in /libs/langgraph (#7574)
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from
1.2.1 to 1.2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/theskumar/python-dotenv/releases">python-dotenv's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.2</h2>
<h3>Added</h3>
<ul>
<li>Support for Python 3.14, including the free-threaded (3.14t) build.
(#)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The <code>dotenv run</code> command now forwards flags directly to
the specified command by <a
href="https://github.com/bbc2"><code>@​bbc2</code></a> in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/607">theskumar/python-dotenv#607</a></li>
<li>Improved documentation clarity regarding override behavior and the
reference page.</li>
<li>Updated PyPy support to version 3.11.</li>
<li>Documentation for FIFO file support.</li>
<li>Support for Python 3.9.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Improved <code>set_key</code> and <code>unset_key</code> behavior
when interacting with symlinks by <a
href="https://github.com/bbc2"><code>@​bbc2</code></a> in <a
href="https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311">#790c5</a></li>
<li>Corrected the license specifier and added missing Python 3.14
classifiers in package metadata by <a
href="https://github.com/JYOuyang"><code>@​JYOuyang</code></a> in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/590">theskumar/python-dotenv#590</a></li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li>
<p><code>dotenv.set_key</code> and <code>dotenv.unset_key</code> used to
follow symlinks in some
situations. This is no longer the case. For that behavior to be restored
in
all cases, <code>follow_symlinks=True</code> should be used.</p>
</li>
<li>
<p>In the CLI, <code>set</code> and <code>unset</code> used to follow
symlinks in some situations. This
is no longer the case.</p>
</li>
<li>
<p><code>dotenv.set_key</code>, <code>dotenv.unset_key</code> and the
CLI commands <code>set</code> and <code>unset</code>
used to reset the file mode of the modified .env file to
<code>0o600</code> in some
situations. This is no longer the case: The original mode of the file is
now
preserved. Is the file needed to be created or wasn't a regular file,
mode
<code>0o600</code> is used.</p>
</li>
</ul>
<h3>Misc</h3>
<ul>
<li>skip 000 permission tests for root user by <a
href="https://github.com/burnout-projects"><code>@​burnout-projects</code></a>
in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/561">theskumar/python-dotenv#561</a></li>
<li>Bump actions/checkout from 5 to 6 in the github-actions group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/593">theskumar/python-dotenv#593</a></li>
<li>Add Windows testing to CI by <a
href="https://github.com/bbc2"><code>@​bbc2</code></a> in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/604">theskumar/python-dotenv#604</a></li>
<li>Improve workflow efficiency with best practices by <a
href="https://github.com/theskumar"><code>@​theskumar</code></a> in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/609">theskumar/python-dotenv#609</a></li>
<li>Remove the use of <code>sh</code> in tests by <a
href="https://github.com/bbc2"><code>@​bbc2</code></a> in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/612">theskumar/python-dotenv#612</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/JYOuyang"><code>@​JYOuyang</code></a>
made their first contribution in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/590">theskumar/python-dotenv#590</a></li>
<li><a
href="https://github.com/burnout-projects"><code>@​burnout-projects</code></a>
made their first contribution in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/561">theskumar/python-dotenv#561</a></li>
<li><a
href="https://github.com/cpackham-atlnz"><code>@​cpackham-atlnz</code></a>
made their first contribution in <a
href="https://redirect.github.com/theskumar/python-dotenv/pull/597">theskumar/python-dotenv#597</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2">https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md">python-dotenv's
changelog</a>.</em></p>
<blockquote>
<h2>[1.2.2] - 2026-03-01</h2>
<h3>Added</h3>
<ul>
<li>Support for Python 3.14, including the free-threaded (3.14t) build.
(<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/588">#588</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The <code>dotenv run</code> command now forwards flags directly to
the specified command by [<a
href="https://github.com/bbc2"><code>@​bbc2</code></a>] in <a
href="https://redirect.github.com/theskumar/python-dotenv/issues/607">#607</a></li>
<li>Improved documentation clarity regarding override behavior and the
reference page.</li>
<li>Updated PyPy support to version 3.11.</li>
<li>Documentation for FIFO file support.</li>
<li>Dropped Support for Python 3.9.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Improved <code>set_key</code> and <code>unset_key</code> behavior
when interacting with symlinks by [<a
href="https://github.com/bbc2"><code>@​bbc2</code></a>] in
[790c5c0]</li>
<li>Corrected the license specifier and added missing Python 3.14
classifiers in package metadata by [<a
href="https://github.com/JYOuyang"><code>@​JYOuyang</code></a>] in <a
href="https://redirect.github.com/theskumar/python-dotenv/issues/590">#590</a></li>
</ul>
<h3>Breaking Changes</h3>
<ul>
<li>
<p><code>dotenv.set_key</code> and <code>dotenv.unset_key</code> used to
follow symlinks in some
situations. This is no longer the case. For that behavior to be restored
in
all cases, <code>follow_symlinks=True</code> should be used.</p>
</li>
<li>
<p>In the CLI, <code>set</code> and <code>unset</code> used to follow
symlinks in some situations. This
is no longer the case.</p>
</li>
<li>
<p><code>dotenv.set_key</code>, <code>dotenv.unset_key</code> and the
CLI commands <code>set</code> and <code>unset</code>
used to reset the file mode of the modified .env file to
<code>0o600</code> in some
situations. This is no longer the case: The original mode of the file is
now
preserved. Is the file needed to be created or wasn't a regular file,
mode
<code>0o600</code> is used.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/36004e0e34be7665ff2b11a8a4005144f76f176d"><code>36004e0</code></a>
Bump version: 1.2.1 → 1.2.2</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/eb202520e5933c9daf42501e1e42fdb0144002c8"><code>eb20252</code></a>
docs: update changelog for v1.2.2</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/790c5c02991100aa1bf41ee5330aca75edc51311"><code>790c5c0</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/43340da220fb4ca4f95357bbe21a3c7f8f1278b1"><code>43340da</code></a>
Remove the use of <code>sh</code> in tests (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/612">#612</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/09d7cee32459e7abdcb5c9d8122a552589c06a9c"><code>09d7cee</code></a>
docs: clarify override behavior and document FIFO support (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/610">#610</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/c8de2887c00198c22842c5ae5e92d1747467363c"><code>c8de288</code></a>
ci: improve workflow efficiency with best practices (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/609">#609</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/7bd9e3dbfedc0983ad7d56d5570013035242bdf4"><code>7bd9e3d</code></a>
Add Windows testing to CI (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/604">#604</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/1baaf04f336072e0ee324d5df9563ec767f14f81"><code>1baaf04</code></a>
Drop Python 3.9 support and update to PyPy 3.11 (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/608">#608</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/4a22cf8993804aeede0c20b75bb1a29d3a99e9dc"><code>4a22cf8</code></a>
ci: enable testing on Python 3.14t (free-threaded) (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/588">#588</a>)</li>
<li><a
href="https://github.com/theskumar/python-dotenv/commit/e2e8e776b42e382ae38b44d3982dd649e7507dd4"><code>e2e8e77</code></a>
Fix license specifier (<a
href="https://redirect.github.com/theskumar/python-dotenv/issues/597">#597</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=python-dotenv&package-manager=uv&previous-version=1.2.1&new-version=1.2.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 20:30:29 -07:00
aeff9549c2 chore: node-level timeouts (#7599)
This PR implements task/node-level timeouts. 

Since python has a terrible multi-processing model, we make two
concessions:
- we only support for async functions/nodes. Sync nodes with a timeout
raise an error at compile time
- we implement this with asyncio wait_for in the async path.
 
Each timed attempt is wrapped in _retry.py, and the timer is reset on
each node-level retry. When the deadline is exceeded LangGraph raises
NodeTimeoutError, clears buffered writes, and prevents any late writes
or child-task scheduling from leaking past the timeout via
_TimedAttemptScope.

The design also adds a timed-attempt observer hook
(CONFIG_KEY_TIMED_ATTEMPT_OBSERVER) that emits start/finish events with
identifiers and deadlines. This means that if you have an orchestrating
process starting a worker process, it can listen to start/end events and
hard-kill the process to enforce a timeout if there is a deadlock.

---------

Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-24 18:27:22 -07:00
ccurmeandGitHub 1a248cba45 release(prebuilt): 1.0.11 (#7610) 2026-04-24 14:16:17 -04:00
45246f6c74 feat(prebuilt): allow ToolNode tools to return list[Command | ToolMessage] (#7596)
## Summary

Extends `ToolNode` so that a single tool invocation can return
`list[Command | ToolMessage]` instead of only a single `Command` or
`ToolMessage`. This brings `ToolNode`'s per-tool-call contract in line
with the rest of LangGraph, where nodes can already return multiple
Commands.

Depends on langchain-ai/langchain#36963 which allows
`list[ToolOutputMixin]` to pass through `BaseTool._format_output`
unchanged.

## Changes

### `libs/prebuilt/langgraph/prebuilt/tool_node.py`

**New list-return gate in `_execute_tool_sync` / `_execute_tool_async`**
— After the existing `Command` and `ToolMessage` checks, a new branch
accepts `list[Command | ToolMessage]` and routes it through
`_validate_tool_command_list`. Lists with non-`Command`/`ToolMessage`
elements raise `TypeError`. Both sync and async paths are updated
symmetrically.

**`_validate_tool_command_list`** — Enforces the terminating-ToolMessage
rule: exactly one `ToolMessage` in the list must carry `tool_call_id ==
<outer_id>` (top-level or nested inside a `Command.update["messages"]`).
Zero or multiple terminators raise `_MissingToolMessageError`.
Individual Commands in the list are validated via the existing
`_validate_tool_command`; when a Command lacks the terminator (which is
allowed since the list-level check handles it), the
`_MissingToolMessageError` is caught and the already-normalized command
from the exception is used.

**`_MissingToolMessageError`** — A `ValueError` subclass raised by
`_validate_tool_command` (and `_validate_tool_command_list`) when no
matching `ToolMessage` is found. Carries the already-normalized command
so callers can recover without re-doing deepcopy/message-conversion
work. Using a typed exception avoids brittle string-matching on error
messages.

**`_combine_tool_outputs`** — Flattens list entries at the top of the
method so downstream combiner logic (parent-`goto` accumulation,
ToolMessage wrapping) is unchanged.

**Response processing moved inside try/except** — In both sync and async
execute methods, the response validation (Command/ToolMessage/list
checks) now runs inside the existing error-handling try block, so
validation errors from the list path go through `_handle_tool_errors`
like other tool errors.

**Return type signatures** widened on `_execute_tool_sync`,
`_execute_tool_async`, `_run_one`, `_arun_one` to include `list[Command
| ToolMessage]`.

### `libs/prebuilt/tests/test_tool_node.py`

New tests covering: valid list returns (top-level terminator, nested
terminator, parent-goto + terminator), regression tests for single
Command/ToolMessage returns, invalid cases (no terminator, multiple
terminators), async parity, integration with mixed list/non-list tool
calls, and `_handle_tool_errors` interaction.

---------

Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
2026-04-23 13:37:45 -07:00
8657df80f3 chore: mixup cli formatting (#7585)
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-22 11:29:23 -07:00
Sydney RunkleandGitHub a529b9bede chore(langgraph): bump version 1.1.8 -> 1.1.9 (#7563)
## Summary

- Bumps `langgraph` patch version from `1.1.8` to `1.1.9` in
`libs/langgraph/pyproject.toml`

## Test plan

- [ ] Verify version string is correct in `pyproject.toml`
- [ ] Confirm release workflow triggers on merge
2026-04-21 09:38:58 -04:00
0a26b471d3 fix(langgraph): don't propagate ReplayState to subgraphs on plain resume (#7561)
**Description:**
When clients resume an interrupted subgraph with `Command(resume=...)`
plus an explicit `checkpoint_id` in the config (the pattern LangGraph
Studio and the API server emit on every resume), the subgraph restarts
from its first node instead of continuing at the interrupted node.

Fix: gate `ReplayState` propagation on `is_time_traveling` rather than
`is_replaying`, so a resume that happens to carry a head checkpoint_id
behaves the same as one with just a thread_id.

**Verification:** added regression test
`test_subgraph_interrupt_resume_with_explicit_head_checkpoint_id` (fails
on main, passes with fix, across memory/sqlite/sqlite_aes).
Full `test_time_travel.py`, `test_time_travel_async.py`,
`test_interruption.py`, and all pregel subgraph/interrupt/resume/replay
tests still pass.

Co-authored-by: Jessie Ibarra <jessie.ibarra@langgraph.dev>
2026-04-20 21:29:18 -04:00
Eugene YurtsevandGitHub b674dd4622 feat(prebuilt): expose available tools on ToolRuntime (#7512) 2026-04-17 16:54:16 -04:00
Eugene YurtsevandGitHub 8df0a377d0 chore(langgraph): undo unnecessary changes in stream handler (#7536)
This change is no longer necessary as langchain-core will continue
copying checkpoint_ns for backwards compatibility
2026-04-17 16:22:34 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
216cf33a54 chore(deps): bump the pip group across 3 directories with 1 update (#7537)
Bumps the pip group with 1 update in the
/libs/cli/examples/graph_prerelease_reqs/deps/zuper_deps directory:
[langchain-openai](https://github.com/langchain-ai/langchain).
Bumps the pip group with 1 update in the
/libs/cli/examples/graph_prerelease_reqs_fail directory:
[langchain-openai](https://github.com/langchain-ai/langchain).
Bumps the pip group with 1 update in the
/libs/cli/examples/graph_prerelease_reqs directory:
[langchain-openai](https://github.com/langchain-ai/langchain).

Updates `langchain-openai` from 1.0.1 to 1.1.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-openai's
releases</a>.</em></p>
<blockquote>
<h2>langchain-openai==1.1.14</h2>
<p>Changes since langchain-openai==1.1.13</p>
<p>release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)
chore(deps): bump pytest to <code>9.0.3</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36801">#36801</a>)
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36795">#36795</a>)
chore: bump pillow from 12.1.1 to 12.2.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36777">#36777</a>)</p>
<h2>langchain-openai==1.1.13</h2>
<p>Changes since langchain-openai==1.1.12</p>
<p>release(openai): 1.1.13 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36729">#36729</a>)
fix(openai): handle content blocks without type key in responses api
conversion (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36725">#36725</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36539">#36539</a>)
chore(openai): fix broken vcr cassette playback and add ci guard (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36502">#36502</a>)
fix(openai,groq,openrouter): use is-not-None checks in usage metadata
token extraction (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36500">#36500</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36455">#36455</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36368">#36368</a>)
fix(openai): update computer call test (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36352">#36352</a>)
fix(openai): let user-provided User-Agent override the Azure default (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35523">#35523</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36248">#36248</a>)</p>
<h2>langchain-openai==1.1.12</h2>
<p>Changes since langchain-openai==1.1.11</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36180">#36180</a>)
release(openai): 1.1.12 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36178">#36178</a>)
fix(core,model-profiles): add missing <code>ModelProfile</code> fields,
warn on schema drift (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36129">#36129</a>)
fix(openai): support phase parameter (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36161">#36161</a>)
fix(openai): preserve namespace field in streaming function_call chunks
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36108">#36108</a>)
ci: suppress pytest streaming output in CI (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36092">#36092</a>)
ci: avoid unnecessary dep installs in lint targets (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36046">#36046</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36039">#36039</a>)
chore: bump orjson from 3.11.5 to 3.11.6 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35860">#35860</a>)
fix(openai): add type: message to Responses API input items (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35693">#35693</a>)
perf(.github): set a timeout on get min versions HTTP calls (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35851">#35851</a>)
feat(model-profiles): new fields + <code>Makefile</code> target (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35788">#35788</a>)
fix(openai): close PIL Image handles in token counting to prevent fd
leak (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35742">#35742</a>)
fix(openai): typo (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35763">#35763</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35754">#35754</a>)</p>
<h2>langchain-openai==1.1.11</h2>
<p>Changes since langchain-openai==1.1.10</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35705">#35705</a>)
release(openai): 1.1.11 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35703">#35703</a>)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b7447c6969fc928ec3f29c200e2e56c0a46c4c77"><code>b7447c6</code></a>
fix(infra): skip serdes tests in min-version release step (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36818">#36818</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/41c0cc58b0dac82000d24715f7a4b44dc8b01fd3"><code>41c0cc5</code></a>
release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/0516156ef98f5001129f6d47bc8682d6536d58fb"><code>0516156</code></a>
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/338aa8131a8124e7aa1e042616ccd2366ff9f699"><code>338aa81</code></a>
fix(core): restore cloud metadata IPs and link-local range in SSRF
policy (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/3">#3</a>...</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/51e954877efd2d2c3c5bf09364dcfec8794eadb0"><code>51e9548</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/text-splitters (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36797">#36797</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/e85c418cfa559d4a794ddc6db92c6febab44651c"><code>e85c418</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/model-profiles (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36798">#36798</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/789126e6c78ad74664bea26228dda6e72e135dce"><code>789126e</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/standard-tests (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36799">#36799</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/937b3eb3827551d17ee4736f9acc4aa57e88c716"><code>937b3eb</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/langchain_v1 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36800">#36800</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a06c205738cf5953e28c37287ddb1559d67c01f6"><code>a06c205</code></a>
ci(infra): validate issue checkboxes by section (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36811">#36811</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/aa33b06deb0d65489ce254b48a8aaf8a86304c18"><code>aa33b06</code></a>
fix(langchain-classic): suppress mypy errors in compat code (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36806">#36806</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-openai==1.0.1...langchain-openai==1.1.14">compare
view</a></li>
</ul>
</details>
<br />

Updates `langchain-openai` from 1.0.0a2 to 1.1.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-openai's
releases</a>.</em></p>
<blockquote>
<h2>langchain-openai==1.1.14</h2>
<p>Changes since langchain-openai==1.1.13</p>
<p>release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)
chore(deps): bump pytest to <code>9.0.3</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36801">#36801</a>)
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36795">#36795</a>)
chore: bump pillow from 12.1.1 to 12.2.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36777">#36777</a>)</p>
<h2>langchain-openai==1.1.13</h2>
<p>Changes since langchain-openai==1.1.12</p>
<p>release(openai): 1.1.13 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36729">#36729</a>)
fix(openai): handle content blocks without type key in responses api
conversion (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36725">#36725</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36539">#36539</a>)
chore(openai): fix broken vcr cassette playback and add ci guard (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36502">#36502</a>)
fix(openai,groq,openrouter): use is-not-None checks in usage metadata
token extraction (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36500">#36500</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36455">#36455</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36368">#36368</a>)
fix(openai): update computer call test (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36352">#36352</a>)
fix(openai): let user-provided User-Agent override the Azure default (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35523">#35523</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36248">#36248</a>)</p>
<h2>langchain-openai==1.1.12</h2>
<p>Changes since langchain-openai==1.1.11</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36180">#36180</a>)
release(openai): 1.1.12 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36178">#36178</a>)
fix(core,model-profiles): add missing <code>ModelProfile</code> fields,
warn on schema drift (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36129">#36129</a>)
fix(openai): support phase parameter (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36161">#36161</a>)
fix(openai): preserve namespace field in streaming function_call chunks
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36108">#36108</a>)
ci: suppress pytest streaming output in CI (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36092">#36092</a>)
ci: avoid unnecessary dep installs in lint targets (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36046">#36046</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36039">#36039</a>)
chore: bump orjson from 3.11.5 to 3.11.6 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35860">#35860</a>)
fix(openai): add type: message to Responses API input items (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35693">#35693</a>)
perf(.github): set a timeout on get min versions HTTP calls (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35851">#35851</a>)
feat(model-profiles): new fields + <code>Makefile</code> target (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35788">#35788</a>)
fix(openai): close PIL Image handles in token counting to prevent fd
leak (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35742">#35742</a>)
fix(openai): typo (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35763">#35763</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35754">#35754</a>)</p>
<h2>langchain-openai==1.1.11</h2>
<p>Changes since langchain-openai==1.1.10</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35705">#35705</a>)
release(openai): 1.1.11 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35703">#35703</a>)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b7447c6969fc928ec3f29c200e2e56c0a46c4c77"><code>b7447c6</code></a>
fix(infra): skip serdes tests in min-version release step (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36818">#36818</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/41c0cc58b0dac82000d24715f7a4b44dc8b01fd3"><code>41c0cc5</code></a>
release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/0516156ef98f5001129f6d47bc8682d6536d58fb"><code>0516156</code></a>
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/338aa8131a8124e7aa1e042616ccd2366ff9f699"><code>338aa81</code></a>
fix(core): restore cloud metadata IPs and link-local range in SSRF
policy (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/3">#3</a>...</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/51e954877efd2d2c3c5bf09364dcfec8794eadb0"><code>51e9548</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/text-splitters (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36797">#36797</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/e85c418cfa559d4a794ddc6db92c6febab44651c"><code>e85c418</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/model-profiles (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36798">#36798</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/789126e6c78ad74664bea26228dda6e72e135dce"><code>789126e</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/standard-tests (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36799">#36799</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/937b3eb3827551d17ee4736f9acc4aa57e88c716"><code>937b3eb</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/langchain_v1 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36800">#36800</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a06c205738cf5953e28c37287ddb1559d67c01f6"><code>a06c205</code></a>
ci(infra): validate issue checkboxes by section (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36811">#36811</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/aa33b06deb0d65489ce254b48a8aaf8a86304c18"><code>aa33b06</code></a>
fix(langchain-classic): suppress mypy errors in compat code (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36806">#36806</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-openai==1.0.1...langchain-openai==1.1.14">compare
view</a></li>
</ul>
</details>
<br />

Updates `langchain-openai` from 1.0.0a2 to 1.1.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-openai's
releases</a>.</em></p>
<blockquote>
<h2>langchain-openai==1.1.14</h2>
<p>Changes since langchain-openai==1.1.13</p>
<p>release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)
chore(deps): bump pytest to <code>9.0.3</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36801">#36801</a>)
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36795">#36795</a>)
chore: bump pillow from 12.1.1 to 12.2.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36777">#36777</a>)</p>
<h2>langchain-openai==1.1.13</h2>
<p>Changes since langchain-openai==1.1.12</p>
<p>release(openai): 1.1.13 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36729">#36729</a>)
fix(openai): handle content blocks without type key in responses api
conversion (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36725">#36725</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36539">#36539</a>)
chore(openai): fix broken vcr cassette playback and add ci guard (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36502">#36502</a>)
fix(openai,groq,openrouter): use is-not-None checks in usage metadata
token extraction (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36500">#36500</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36455">#36455</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36368">#36368</a>)
fix(openai): update computer call test (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36352">#36352</a>)
fix(openai): let user-provided User-Agent override the Azure default (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35523">#35523</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36248">#36248</a>)</p>
<h2>langchain-openai==1.1.12</h2>
<p>Changes since langchain-openai==1.1.11</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36180">#36180</a>)
release(openai): 1.1.12 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36178">#36178</a>)
fix(core,model-profiles): add missing <code>ModelProfile</code> fields,
warn on schema drift (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36129">#36129</a>)
fix(openai): support phase parameter (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36161">#36161</a>)
fix(openai): preserve namespace field in streaming function_call chunks
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36108">#36108</a>)
ci: suppress pytest streaming output in CI (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36092">#36092</a>)
ci: avoid unnecessary dep installs in lint targets (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36046">#36046</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36039">#36039</a>)
chore: bump orjson from 3.11.5 to 3.11.6 in /libs/partners/openai (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35860">#35860</a>)
fix(openai): add type: message to Responses API input items (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35693">#35693</a>)
perf(.github): set a timeout on get min versions HTTP calls (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35851">#35851</a>)
feat(model-profiles): new fields + <code>Makefile</code> target (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35788">#35788</a>)
fix(openai): close PIL Image handles in token counting to prevent fd
leak (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35742">#35742</a>)
fix(openai): typo (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35763">#35763</a>)
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35754">#35754</a>)</p>
<h2>langchain-openai==1.1.11</h2>
<p>Changes since langchain-openai==1.1.10</p>
<p>fix(openai): bump min core version (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35705">#35705</a>)
release(openai): 1.1.11 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/35703">#35703</a>)</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b7447c6969fc928ec3f29c200e2e56c0a46c4c77"><code>b7447c6</code></a>
fix(infra): skip serdes tests in min-version release step (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36818">#36818</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/41c0cc58b0dac82000d24715f7a4b44dc8b01fd3"><code>41c0cc5</code></a>
release(openai): 1.1.14 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36820">#36820</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/0516156ef98f5001129f6d47bc8682d6536d58fb"><code>0516156</code></a>
fix(openai): use SSRF-safe transport for image token counting (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36819">#36819</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/338aa8131a8124e7aa1e042616ccd2366ff9f699"><code>338aa81</code></a>
fix(core): restore cloud metadata IPs and link-local range in SSRF
policy (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/3">#3</a>...</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/51e954877efd2d2c3c5bf09364dcfec8794eadb0"><code>51e9548</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/text-splitters (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36797">#36797</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/e85c418cfa559d4a794ddc6db92c6febab44651c"><code>e85c418</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/model-profiles (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36798">#36798</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/789126e6c78ad74664bea26228dda6e72e135dce"><code>789126e</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/standard-tests (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36799">#36799</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/937b3eb3827551d17ee4736f9acc4aa57e88c716"><code>937b3eb</code></a>
chore: bump langsmith from 0.6.3 to 0.7.31 in /libs/langchain_v1 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36800">#36800</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/a06c205738cf5953e28c37287ddb1559d67c01f6"><code>a06c205</code></a>
ci(infra): validate issue checkboxes by section (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36811">#36811</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/aa33b06deb0d65489ce254b48a8aaf8a86304c18"><code>aa33b06</code></a>
fix(langchain-classic): suppress mypy errors in compat code (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36806">#36806</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-openai==1.0.1...langchain-openai==1.1.14">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-17 12:42:55 -07:00
Eugene YurtsevandGitHub 4956134a37 release(langgraph): 1.1.8 (#7545)
releasing 1.1.8
2026-04-17 19:41:13 +00:00
aa94790f36 fix(langgraph): remove strict add_handler type check that breaks OTel instrumentation (#7544)
## Summary

Removes the `add_handler()` overrides on `_GraphCallbackManager` and
`_AsyncGraphCallbackManager` that reject handlers not inheriting from
`GraphCallbackHandler`. This fixes a regression in 1.1.7 where
`opentelemetry-instrumentation-langchain` (and likely other libraries
that patch `BaseCallbackManager.__init__`) crash with `TypeError:
handlers must inherit GraphCallbackHandler` at invocation time.

## Why this is safe

The strict type check is redundant — `_configure_graph_callbacks` and
`_filter_graph_handlers` already filter handlers to
`GraphCallbackHandler` instances at construction time. Non-graph
handlers that enter via external patches (like OTel's monkey-patch) are
harmless because `handle_event("on_interrupt", ...)` /
`handle_event("on_resume", ...)` will simply no-op on handlers that
don't implement those methods.

## What changed

- Deleted `add_handler()` override from `_GraphCallbackManager` (was
lines 248-255)
- Deleted `add_handler()` override from `_AsyncGraphCallbackManager`
(was lines 324-331)
- No other changes — 18 lines removed, 0 added

## Test plan

- [x] All 8 existing `test_graph_callbacks.py` tests pass (`make test
TEST=tests/test_graph_callbacks.py`)
- [x] `make lint` passes
- [x] `make format` passes (no changes needed)
- [x] Verified fix locally: `LangchainInstrumentor().instrument()` +
`create_react_agent()` + `graph.ainvoke()` no longer raises `TypeError`
- [x] Verified the graph lifecycle callbacks (`on_interrupt`,
`on_resume`) still work correctly

Closes #7543

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2026-04-17 15:33:10 -04:00
ccurmeandGitHub e002711ede release(prebuilt): 1.0.10 (#7541) 2026-04-17 13:52:20 -04:00
f44b49b33d chore: dedup warnings (#7257)
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-17 10:13:03 -07:00
a0a95df2ac release(cli): 0.4.23 (#7542)
Release Note: Increase the max bound for langgraph-api

Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-17 16:38:14 +00:00
Eugene YurtsevandGitHub d194c18c06 release(langgraph): 1.1.7 (#7540)
release langgraph 1.1.7
2026-04-17 15:19:18 +00:00
Sydney Runkle eae916719f CLI bump 2026-04-16 13:48:02 -04:00
f093702e4e fix(prebuilt): handle injected NotRequired keys (#7392)
Resolves https://github.com/langchain-ai/langchain/issues/35585

This would previously raise KeyError:
```python
from typing import Annotated

from langchain_core.tools import tool
from langchain.agents import create_agent
from typing_extensions import NotRequired
from langgraph.prebuilt import InjectedState
from langchain.agents import AgentState


class CustomAgentState(AgentState):
    city: NotRequired[str]


@tool
def get_weather(city: Annotated[str | None, InjectedState("city")] = None) -> str:
    """Get weather for a given city."""
    if city is None:
        city = "Boston"
    return f"It's always sunny in {city}!"


agent = create_agent(
    model="claude-sonnet-4-6",
    tools=[get_weather],
    system_prompt="You are a helpful assistant",
    state_schema=CustomAgentState,
)

input_message = {
    "role": "user",
    "content": "What's the weather?",
}

result = agent.invoke({"messages": [input_message]})
for m in result["messages"]:
    m.pretty_print()
```

---------

Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
2026-04-16 13:06:33 +00:00
Sydney RunkleandGitHub 51cbdbd5cd fix: time travel when going back to interrupt node (#7498)
# Fix: Create fork checkpoint on subgraph time travel

## Problem

When time-traveling to a subgraph checkpoint that has an interrupt, and
then resuming, the resume would load the **wrong state** — it would pick
up the original execution's latest checkpoint instead of the
time-traveled one.

This happened because replaying from a subgraph checkpoint never created
a new parent checkpoint. If the replay hit an interrupt before
`after_tick()` ran, no checkpoint was written at all, so the parent's
"latest" checkpoint was still the old one from the original execution.

## Fix

When the loop detects a time-travel replay (not an `update_state` fork),
it now **eagerly writes a fork checkpoint** at the start of the tick.
This ensures:

1. The parent thread's latest checkpoint points to the replayed state
2. Subsequent `Command(resume=...)` calls find the correct checkpoint
3. Stale `INTERRUPT` pending writes from the old checkpoint are cleared
(they reference old task IDs)

Additionally, the subgraph replay logic now uses the **parent checkpoint
ID** (from `prev_checkpoint_config`) when resolving subgraph checkpoints
during time-travel, matching the existing behavior for `update_state`
forks.

## Checkpoint flow diagrams

### Before fix: time travel leaves no fork

```
Original execution:

  C0 (start) --> C1 (step_a) --> C2 (ask_1 interrupt) --> C3 (resume) --> C4 (ask_2 interrupt) --> C5 (done)

Time travel to C2 (subgraph config):

  Replay runs... hits interrupt... no new checkpoint written.
  Parent "latest" is still C5.

  Command(resume="new_answer"):
    Loads C5 (wrong!) instead of the replayed C2 state.
```

### After fix: time travel creates a fork

```
Original execution:

  C0 --> C1 --> C2 --> C3 --> C4 --> C5 (done)

Time travel to C2 (subgraph config):

  C0 --> C1 --> C2 --> C3 --> C4 --> C5
                  \
                   F1 (fork, source="fork")  <-- new latest

  Command(resume="new_answer"):
    Loads F1 (correct!) --> resumes from the right state.

  After full resume:

  C0 --> C1 --> C2 --> C3 --> C4 --> C5
                  \
                   F1 --> F2 (ask_1 result) --> F3 (ask_2 interrupt) --> F4 (done)
```

### Manual fork via `update_state` (unchanged)

```
  C0 --> C1 --> C2 --> C3
                  \
                   U1 (source="update")  <-- created by update_state()

  This path already worked. The fix skips update/fork sources
  so existing behavior is preserved.
```

## Changes

- **`libs/langgraph/langgraph/pregel/_loop.py`**:
- Extract `is_time_traveling` flag from the existing replay detection
logic for reuse
- Write a fork checkpoint (`source="fork"`) eagerly at the start of a
time-travel tick, before execution begins
- Clear stale `INTERRUPT` pending writes when creating the fork (they
reference old task IDs that won't match the new checkpoint)
- Unify subgraph replay ID resolution: check `source in ("update",
"fork")` instead of a separate `is_time_traveling` condition, since the
new fork checkpoint now has `source="fork"`
- **`libs/langgraph/tests/test_time_travel.py`** and
**`test_time_travel_async.py`**: Added 4 new test cases (sync + async):
- `test_replay_from_before_interrupt_then_resume` — replays from a
checkpoint before an interrupt, resumes with a new answer, and verifies
the full checkpoint history (source, next, values) at each stage
- `test_subgraph_time_travel_resume_from_first_interrupt` — time-travels
to a subgraph's first interrupt, resumes both interrupts with new
answers, and verifies the fork creates a new branch while preserving the
original
- `test_subgraph_time_travel_resume_from_second_interrupt` —
time-travels to a subgraph's second interrupt, resumes with a new
answer, and verifies the first interrupt's original answer is preserved
- `test_subgraph_time_travel_checkpoint_pattern` — verifies the fork
checkpoint branches from the correct replay point and that the full
checkpoint tree is correct after resume
- **`libs/langgraph/tests/test_pregel.py`** /
**`test_pregel_async.py`**: Updated existing
`test_weather_subgraph_state` to account for the new fork checkpoint
appearing in history (history length increases by 1)
2026-04-16 08:29:48 -04:00
hari-dhanushkodiandGitHub 4d64227c13 chore: start tracking cli deploy source (#7520)
Fixes #

<!-- Replace everything above this line with a 1-2 sentence description
of your change. Keep the "Fixes #xx" keyword and update the issue
number. -->

Read the full contributing guidelines:
https://docs.langchain.com/oss/python/contributing/overview

> **All contributions must be in English.** See the [language
policy](https://docs.langchain.com/oss/python/contributing/overview#language-policy).

If you paste a large clearly AI generated description here your PR may
be IGNORED or CLOSED!

Thank you for contributing to LangGraph! Follow these steps to have your
pull request considered as ready for review.

1. PR title: Should follow the format: TYPE(SCOPE): DESCRIPTION

    - feat(langgraph): add multi-tenant support
- Allowed TYPE and SCOPE values:
https://github.com/langchain-ai/langgraph/blob/main/.github/workflows/pr_lint.yml#L19-L43

2. PR description:

  - Write 1-2 sentences summarizing the change.
- The `Fixes #xx` line at the top is **required** for external
contributions — update the issue number and keep the keyword. This links
your PR to the approved issue and auto-closes it on merge.
  - If there are any breaking changes, please clearly describe them.
- If this PR depends on another PR being merged first, please include
"Depends on #PR_NUMBER" in the description.

3. Run `make format`, `make lint` and `make test` from the root of the
package(s) you've modified.

  - We will not consider a PR unless these three are passing in CI.

4. How did you verify your code works?

Additional guidelines:

- All external PRs must link to an issue or discussion where a solution
has been approved by a maintainer, and you must be assigned to that
issue. PRs without prior approval will be closed.
- PRs should not touch more than one package unless absolutely
necessary.
- Do not update the `uv.lock` files or add dependencies to
`pyproject.toml` files (even optional ones) unless you have explicit
permission to do so by a maintainer.

## Social handles (optional)
<!-- If you'd like a shoutout on release, add your socials below -->
Twitter: @
LinkedIn: https://linkedin.com/in/
2026-04-16 08:24:47 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6bcac5d72e chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/prebuilt (#7530)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.6.4 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.4...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.6.4&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:37:47 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6177c4311b chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/checkpoint (#7525)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.6.4 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.4...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.6.4&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:37:35 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
303769904b chore(deps): bump langsmith from 0.7.20 to 0.7.31 in /libs/sdk-py (#7528)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.7.20 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.20...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.7.20&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:37:27 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cee7dcd523 chore(deps): bump langsmith from 0.7.26 to 0.7.31 in /libs/cli (#7529)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.7.26 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.26...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.7.26&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:37:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3413723e5a chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/checkpoint-postgres (#7527)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.6.4 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.4...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.6.4&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:37:03 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
07252d2cda chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/langgraph (#7526)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.6.4 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.4...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.6.4&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:36:52 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
47fd42abb2 chore(deps): bump langsmith from 0.6.4 to 0.7.31 in /libs/checkpoint-sqlite (#7524)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.6.4 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.6.4...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.6.4&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:36:42 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
554b2db1f8 chore(deps): bump langsmith from 0.7.3 to 0.7.31 in /libs/checkpoint-conformance (#7523)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.7.3 to 0.7.31.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.3...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=uv&previous-version=0.7.3&new-version=0.7.31)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:36:27 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
93a144b404 chore(deps): bump the uv group across 2 directories with 1 update (#7531)
Bumps the uv group with 1 update in the /libs/cli/uv-examples/monorepo
directory: [langsmith](https://github.com/langchain-ai/langsmith-sdk).
Bumps the uv group with 1 update in the /libs/cli/uv-examples/simple
directory: [langsmith](https://github.com/langchain-ai/langsmith-sdk).

Updates `langsmith` from 0.7.26 to 0.7.31
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.26...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />

Updates `langsmith` from 0.7.26 to 0.7.31
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langsmith-sdk/releases">langsmith's
releases</a>.</em></p>
<blockquote>
<h2>v0.7.31</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps-dev): bump langchain-core from 1.2.23 to 1.2.28 in
/python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2692">langchain-ai/langsmith-sdk#2692</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.82.0 to
0.84.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2684">langchain-ai/langsmith-sdk#2684</a></li>
<li>chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /python by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2693">langchain-ai/langsmith-sdk#2693</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.84.0 to
0.85.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2700">langchain-ai/langsmith-sdk#2700</a></li>
<li>feat(py): Tag OpenAI Agent Python SDK runs with ls_agent_type by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2699">langchain-ai/langsmith-sdk#2699</a></li>
<li>feat(js): Adds ls_agent_type metadata to AI SDK runs by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2701">langchain-ai/langsmith-sdk#2701</a></li>
<li>chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to
4.67.3.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2710">langchain-ai/langsmith-sdk#2710</a></li>
<li>chore(deps): bump pnpm/action-setup from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2705">langchain-ai/langsmith-sdk#2705</a></li>
<li>chore(deps): bump the py-minor-and-patch group across 1 directory
with 10 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2711">langchain-ai/langsmith-sdk#2711</a></li>
<li>chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2702">langchain-ai/langsmith-sdk#2702</a></li>
<li>chore(deps): bump actions/github-script from 8 to 9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2706">langchain-ai/langsmith-sdk#2706</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 7 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2712">langchain-ai/langsmith-sdk#2712</a></li>
<li>chore(deps-dev): bump types-psutil from 7.2.2.20260130 to
7.2.2.20260408 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2709">langchain-ai/langsmith-sdk#2709</a></li>
<li>chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2708">langchain-ai/langsmith-sdk#2708</a></li>
<li>feat: Filter kwargs from new token events by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2714">langchain-ai/langsmith-sdk#2714</a></li>
<li>release(py): 0.7.31 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2716">langchain-ai/langsmith-sdk#2716</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.30...v0.7.31</a></p>
<h2>v0.7.30</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(python): add service feature to sandbox by <a
href="https://github.com/DanielKneipp"><code>@​DanielKneipp</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2665">langchain-ai/langsmith-sdk#2665</a></li>
<li>fix(js): Fix prototype pollution bug in anonymizers by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2690">langchain-ai/langsmith-sdk#2690</a></li>
<li>release(js): 0.5.18 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2691">langchain-ai/langsmith-sdk#2691</a></li>
<li>chore(js/sandbox): suppress warning log by <a
href="https://github.com/hntrl"><code>@​hntrl</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2694">langchain-ai/langsmith-sdk#2694</a></li>
<li>feat(js): Add metadata to Claude Agent SDK JS tracing by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2695">langchain-ai/langsmith-sdk#2695</a></li>
<li>fix(py): Fix run tree memory leak by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2696">langchain-ai/langsmith-sdk#2696</a></li>
<li>release(py): 0.7.30 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2698">langchain-ai/langsmith-sdk#2698</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.29...v0.7.30</a></p>
<h2>v0.7.29</h2>
<h2>What's Changed</h2>
<ul>
<li>release(js): 0.5.17 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2681">langchain-ai/langsmith-sdk#2681</a></li>
<li>feat(py): Fix race condition around Claude Agent SDK instrumentation
by <a href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a>
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2685">langchain-ai/langsmith-sdk#2685</a></li>
<li>release(py): 0.7.29 by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2686">langchain-ai/langsmith-sdk#2686</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29">https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.28...v0.7.29</a></p>
<h2>v0.7.28</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(py): Support subagent tracing in Claude Agents SDK, fix usage
and duplicate messages by <a
href="https://github.com/jacoblee93"><code>@​jacoblee93</code></a> in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2670">langchain-ai/langsmith-sdk#2670</a></li>
<li>chore(deps-dev): bump the py-minor-and-patch group across 1
directory with 11 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2677">langchain-ai/langsmith-sdk#2677</a></li>
<li>chore(deps-dev): bump the js-minor-and-patch group across 1
directory with 8 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2667">langchain-ai/langsmith-sdk#2667</a></li>
<li>chore(deps): bump pnpm/action-setup from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/pull/2658">langchain-ai/langsmith-sdk#2658</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/c434999d05c00334efeba88b8bbd2de9f3afbef6"><code>c434999</code></a>
release(py): 0.7.31 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2716">#2716</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/47d7c4a783333e716395d802e7632f1f1b4744d3"><code>47d7c4a</code></a>
feat: Filter kwargs from new token events (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/3c57445b543c9a2f86db52024ea2c998bfc2ffab"><code>3c57445</code></a>
chore(deps-dev): bump rich from 14.3.3 to 15.0.0 in /python (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2708">#2708</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/2be6cd01a2b6e35e811488d3561e7b0b57b06f63"><code>2be6cd0</code></a>
chore(deps-dev): bump types-psutil from 7.2.2.20260130 to 7.2.2.20260408
in /...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/b8b6ca32d43c919c07a4e13c99a83bcaab8accb0"><code>b8b6ca3</code></a>
chore(deps-dev): bump the js-minor-and-patch group across 1 directory
with 7 ...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9897cb33da7698291637f268edd833ca3e1adde6"><code>9897cb3</code></a>
chore(deps): bump actions/github-script from 8 to 9 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2706">#2706</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/572c0184285747e027a796e03ea6c9ba171e09a6"><code>572c018</code></a>
chore(deps-dev): bump <code>@​anthropic-ai/sdk</code> from 0.85.0 to
0.86.0 in /js (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2702">#2702</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/57447524c88b6bba2775161aa449da32fb8e5c42"><code>5744752</code></a>
chore(deps): bump the py-minor-and-patch group across 1 directory with
10 upd...</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/960cae7f490e9ccbe428e6b56c8047bdb7b942a5"><code>960cae7</code></a>
chore(deps): bump pnpm/action-setup from 5 to 6 (<a
href="https://redirect.github.com/langchain-ai/langsmith-sdk/issues/2705">#2705</a>)</li>
<li><a
href="https://github.com/langchain-ai/langsmith-sdk/commit/9370e7670abf7f8f9a36fbb72250bcfd2f91e7c6"><code>9370e76</code></a>
chore(deps-dev): bump types-tqdm from 4.67.3.20260303 to 4.67.3.20260408
in /...</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langsmith-sdk/compare/v0.7.26...v0.7.31">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:36:05 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2f4611db8f chore(deps): bump langsmith from 0.5.18 to 0.5.20 in /libs/cli/js-examples (#7522)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.5.18 to 0.5.20.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langsmith-sdk/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=npm_and_yarn&previous-version=0.5.18&new-version=0.5.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:21:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
20c5d36efe chore(deps): bump langsmith from 0.5.18 to 0.5.20 in /libs/cli/js-monorepo-example (#7521)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.5.18 to 0.5.20.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langsmith-sdk/commits">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=npm_and_yarn&previous-version=0.5.18&new-version=0.5.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-15 19:21:00 -07:00
25470ea435 release(checkpoint): 4.0.2 (#7518)
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-15 20:52:57 +00:00
7fa49bd550 docs: document LANGGRAPH_STRICT_MSGPACK for checkpoint security (#7517)
## Summary

- Add `LANGGRAPH_STRICT_MSGPACK=true` guidance to `JsonPlusSerializer`
docstring and inline comments
- Update the warning message emitted for unregistered types to mention
the env var
- Add module docstring to `_msgpack.py` explaining the safety controls
- Add Security sections to checkpoint, checkpoint-postgres, and
checkpoint-sqlite READMEs

## Context

Multiple security advisories have reported the same msgpack
deserialization pattern (`ext_hook` → `importlib.import_module` →
`getattr` → call). The underlying behavior is documented in the repo's
threat model as T1, but the `LANGGRAPH_STRICT_MSGPACK` env var that
mitigates it is not surfaced in user-facing docs, docstrings, or warning
messages. This PR closes that gap.

## Test plan

- [x] Verify READMEs render correctly on GitHub (callout boxes use `>
[!IMPORTANT]` syntax)
- [x] Verify `JsonPlusSerializer` docstring renders in IDE tooltips
- [x] Confirm warning message format: `LANGGRAPH_STRICT_MSGPACK=true
PYTHON_CMD 2>&1 | grep -i strict`

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:05:55 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6719d34023 chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/checkpoint-sqlite (#7502)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 17:39:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedy
96843788d0 chore(deps): bump langchain-core from 1.2.27 to 1.2.28 in /libs/cli (#7450)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.27 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li>See full diff in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.27...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.27&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-04-14 10:39:03 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ba5e3c4a9b chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/checkpoint (#7506)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:38:29 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9e9783b156 chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/prebuilt (#7505)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:38:19 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c27d103e04 chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/checkpoint-conformance (#7508)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:35:00 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d189f6551e chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/langgraph (#7507)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:34:46 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1dd9adf833 chore(deps): bump pytest from 9.0.2 to 9.0.3 in /libs/checkpoint-postgres (#7503)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:34:34 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedy
92c66ca997 chore(deps): bump langchain-core from 1.2.22 to 1.2.28 in /libs/checkpoint-sqlite (#7451)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.22 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
<h2>langchain-core==1.2.27</h2>
<p>Changes since langchain-core==1.2.26</p>
<p>release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)
chore: add comment explaining <code>pygments&gt;=2.20.0</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36570">#36570</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>.</p>
<h2>langchain-core==1.2.26</h2>
<p>Changes since langchain-core==1.2.25</p>
<p>release(core): 1.2.26 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36511">#36511</a>)
fix(core): add init validator and serialization mappings for Bedrock
models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34510">#34510</a>)
feat(core): add <code>ChatBaseten</code> to serializable mapping (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36510">#36510</a>)
chore(core): drop <code>gpt-3.5-turbo</code> from docstrings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36497">#36497</a>)
fix(core): correct parameter names in filter_messages docstring example
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36462">#36462</a>)</p>
<h2>langchain-core==1.2.25</h2>
<p>Changes since langchain-core==1.2.24</p>
<p>release(core): 1.2.25 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36473">#36473</a>)
fix(core): harden check for txt files in deprecated prompt loading
functions (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue resolved in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>.</p>
<h2>langchain-core==1.2.24</h2>
<p>Changes since langchain-core==1.2.23</p>
<p>release(core): 1.2.24 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36434">#36434</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
fix(core): add &quot;computer&quot; to _WellKnownOpenAITools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36261">#36261</a>)</p>
<h2>langchain-core==1.2.23</h2>
<p>Changes since langchain-core==1.2.22</p>
<p>release(core): 1.2.23 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36323">#36323</a>)
revert: Revert &quot;fix(core): trace invocation params in
metadata&quot; (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36322">#36322</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36243">#36243</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/64864041168606535dfbd39055c0dca3dd61b5ba"><code>6486404</code></a>
release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7629c747260cbaed7ca55466d5b9e1b520a7de77"><code>7629c74</code></a>
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ce21bf469d7493f4716bc30feb15a5b3f16ebe1e"><code>ce21bf4</code></a>
ci: convert working-directory to validated dropdown (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36575">#36575</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8698eacbd2960c7e3195018f42992bf2c9d69c7"><code>b8698ea</code></a>
release(ollama): 1.1.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36574">#36574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3beba77e2e23d498fda07f9b8d6ba00aabfaf69f"><code>3beba77</code></a>
feat(ollama): support <code>response_format</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34612">#34612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.22...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.22&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-04-14 10:32:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a7356edf8a chore(deps): bump langsmith from 0.5.4 to 0.5.18 in /libs/cli/js-examples (#7474)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.5.4 to 0.5.18.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langsmith-sdk/commits">compare
view</a></li>
</ul>
</details>
<details>
<summary>Install script changes</summary>
<p>This version modifies <code>prepublish</code> script that runs during
installation. Review the package contents before updating.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=npm_and_yarn&previous-version=0.5.4&new-version=0.5.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:25:09 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
354dceaac7 chore(deps-dev): bump pytest from 9.0.2 to 9.0.3 in /libs/sdk-py (#7504)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to
9.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pytest-dev/pytest/releases">pytest's
releases</a>.</em></p>
<blockquote>
<h2>9.0.3</h2>
<h1>pytest 9.0.3 (2026-04-07)</h1>
<h2>Bug fixes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12444">#12444</a>:
Fixed <code>pytest.approx</code> which now correctly takes into account
<code>~collections.abc.Mapping</code> keys order to compare them.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13634">#13634</a>:
Blocking a <code>conftest.py</code> file using the <code>-p no:</code>
option is now explicitly disallowed.</p>
<p>Previously this resulted in an internal assertion failure during
plugin loading.</p>
<p>Pytest now raises a clear <code>UsageError</code> explaining that
conftest files are not plugins and cannot be disabled via
<code>-p</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13734">#13734</a>:
Fixed crash when a test raises an exceptiongroup with
<code>__tracebackhide__ = True</code>.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14195">#14195</a>:
Fixed an issue where non-string messages passed to <!-- raw HTML omitted
-->unittest.TestCase.subTest()<!-- raw HTML omitted --> were not
printed.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>:
Fixed use of insecure temporary directory (CVE-2025-71176).</p>
</li>
</ul>
<h2>Improved documentation</h2>
<ul>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13388">#13388</a>:
Clarified documentation for <code>-p</code> vs
<code>PYTEST_PLUGINS</code> plugin loading and fixed an incorrect
<code>-p</code> example.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/13731">#13731</a>:
Clarified that capture fixtures (e.g. <code>capsys</code> and
<code>capfd</code>) take precedence over the <code>-s</code> /
<code>--capture=no</code> command-line options in <code>Accessing
captured output from a test function
&lt;accessing-captured-output&gt;</code>.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14088">#14088</a>:
Clarified that the default <code>pytest_collection</code> hook sets
<code>session.items</code> before it calls
<code>pytest_collection_finish</code>, not after.</li>
<li><a
href="https://redirect.github.com/pytest-dev/pytest/issues/14255">#14255</a>:
TOML integer log levels must be quoted: Updating reference
documentation.</li>
</ul>
<h2>Contributor-facing changes</h2>
<ul>
<li>
<p><a
href="https://redirect.github.com/pytest-dev/pytest/issues/12689">#12689</a>:
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible <a
href="https://app.codecov.io/gh/pytest-dev/pytest/tests">on the web
interface</a>.</p>
<p>-- by <code>aleguy02</code></p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pytest-dev/pytest/commit/a7d58d7a21b78581e636bbbdea13c66ad1657c1e"><code>a7d58d7</code></a>
Prepare release version 9.0.3</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/089d98199c253d8f89a040243bc4f2aa6cd5ab22"><code>089d981</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14366">#14366</a>
from bluetech/revert-14193-backport</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/8127eaf4ab7f6b2fdd0dc1b38343ec97aeef05ac"><code>8127eaf</code></a>
Revert &quot;Fix: assertrepr_compare respects dict insertion order (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14050">#14050</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14193">#14193</a>)&quot;</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/99a7e6029e7a6e8d53e5df114b1346e035370241"><code>99a7e60</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14363">#14363</a>
from pytest-dev/patchback/backports/9.0.x/95d8423bd...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/ddee02a578da30dd43aedc39c1c1f1aaadfcee95"><code>ddee02a</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14343">#14343</a>
from bluetech/cve-2025-71176-simple</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/74eac6916fee34726cb194f16c516e96fbd29619"><code>74eac69</code></a>
doc: Update training info (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14298">#14298</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14301">#14301</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/f92dee777cfdb77d1c43633d02766ddf1f07c869"><code>f92dee7</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14267">#14267</a>
from pytest-dev/patchback/backports/9.0.x/d6fa26c62...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/7ee58acc8777c31ac6cf388d01addf5a414a7439"><code>7ee58ac</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/12378">#12378</a>
from Pierre-Sassoulas/fix-implicit-str-concat-and-d...</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/37da870d37e3a2f5177cae075c7b9ae279432bf8"><code>37da870</code></a>
Merge pull request <a
href="https://redirect.github.com/pytest-dev/pytest/issues/14259">#14259</a>
from mitre88/patch-4 (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14268">#14268</a>)</li>
<li><a
href="https://github.com/pytest-dev/pytest/commit/c34bfa3b7acb65b594707c714f1d8461b0304eed"><code>c34bfa3</code></a>
Add explanation for string context diffs (<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14257">#14257</a>)
(<a
href="https://redirect.github.com/pytest-dev/pytest/issues/14266">#14266</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest&package-manager=uv&previous-version=9.0.2&new-version=9.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 10:24:27 -07:00
Eugene YurtsevandGitHub 2ff294af77 release(langgraph): 1.1.7a2 (#7511)
Release 1.1.7a2
2026-04-14 16:55:35 +00:00
4c67f84016 chore: allow passing some metadata only for tracing purposes (#7383)
Allows us to put some more information for tracing purposes (e.g.,
ls_integration) without dumping it into the streaming APIs (good for
performance)
Move some other metadata into tracing only since it's not needed in
streaming APIs

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2026-04-14 09:58:24 -04:00
open-swe[bot]GitHubopen-swe[bot] <open-swe@users.noreply.github.com>Sydney Runkle
2c98c59fca fix: populate assistant_id from config configurable instead of metadata (#7468)
## Description
The `_build_server_info` function was reading `assistant_id` and
`graph_id` from `config["metadata"]`, but the server puts these values
in `config["configurable"]`. This updates the source to read from
`configurable` consistently.

## Test Plan
- [ ] Verify `server_info.assistant_id` and `server_info.graph_id` are
correctly populated from `config["configurable"]`

_Opened collaboratively by Sydney Runkle and open-swe._

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
2026-04-13 09:08:09 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d27d4b2d98 chore(deps): bump langsmith from 0.5.4 to 0.5.18 in /libs/cli/js-monorepo-example (#7475)
Bumps [langsmith](https://github.com/langchain-ai/langsmith-sdk) from
0.5.4 to 0.5.18.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/langchain-ai/langsmith-sdk/commits">compare
view</a></li>
</ul>
</details>
<details>
<summary>Install script changes</summary>
<p>This version modifies <code>prepublish</code> script that runs during
installation. Review the package contents before updating.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langsmith&package-manager=npm_and_yarn&previous-version=0.5.4&new-version=0.5.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 14:29:04 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
742d165acb chore(deps): bump uv from 0.11.3 to 0.11.6 in /libs/cli (#7472)
Bumps [uv](https://github.com/astral-sh/uv) from 0.11.3 to 0.11.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">uv's
releases</a>.</em></p>
<blockquote>
<h2>0.11.6</h2>
<h2>Release Notes</h2>
<p>Released on 2026-04-09.</p>
<p>This release resolves a low severity security advisory in which
wheels with malformed RECORD entries could delete arbitrary files on
uninstall. See <a
href="https://github.com/astral-sh/uv/security/advisories/GHSA-pjjw-68hj-v9mw">GHSA-pjjw-68hj-v9mw</a>
for details.</p>
<h3>Bug fixes</h3>
<ul>
<li>Do not remove files outside the venv on uninstall (<a
href="https://redirect.github.com/astral-sh/uv/pull/18942">#18942</a>)</li>
<li>Validate and heal wheel <code>RECORD</code> during installation (<a
href="https://redirect.github.com/astral-sh/uv/pull/18943">#18943</a>)</li>
<li>Avoid <code>uv cache clean</code> errors due to Win32 path
normalization (<a
href="https://redirect.github.com/astral-sh/uv/pull/18856">#18856</a>)</li>
</ul>
<h2>Install uv 0.11.6</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>powershell -ExecutionPolicy Bypass -c &quot;irm
https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-installer.ps1
| iex&quot;
</code></pre>
<h2>Download uv 0.11.6</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Platform</th>
<th>Checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-apple-darwin.tar.gz">uv-aarch64-apple-darwin.tar.gz</a></td>
<td>Apple Silicon macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-apple-darwin.tar.gz">uv-x86_64-apple-darwin.tar.gz</a></td>
<td>Intel macOS</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-apple-darwin.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-pc-windows-msvc.zip">uv-aarch64-pc-windows-msvc.zip</a></td>
<td>ARM64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-pc-windows-msvc.zip">uv-i686-pc-windows-msvc.zip</a></td>
<td>x86 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-pc-windows-msvc.zip">uv-x86_64-pc-windows-msvc.zip</a></td>
<td>x64 Windows</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-pc-windows-msvc.zip.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-unknown-linux-gnu.tar.gz">uv-aarch64-unknown-linux-gnu.tar.gz</a></td>
<td>ARM64 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-unknown-linux-gnu.tar.gz">uv-i686-unknown-linux-gnu.tar.gz</a></td>
<td>x86 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-powerpc64le-unknown-linux-gnu.tar.gz">uv-powerpc64le-unknown-linux-gnu.tar.gz</a></td>
<td>PPC64LE Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-powerpc64le-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-riscv64gc-unknown-linux-gnu.tar.gz">uv-riscv64gc-unknown-linux-gnu.tar.gz</a></td>
<td>RISCV Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-riscv64gc-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-s390x-unknown-linux-gnu.tar.gz">uv-s390x-unknown-linux-gnu.tar.gz</a></td>
<td>S390x Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-s390x-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-unknown-linux-gnu.tar.gz">uv-x86_64-unknown-linux-gnu.tar.gz</a></td>
<td>x64 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-unknown-linux-gnu.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-armv7-unknown-linux-gnueabihf.tar.gz">uv-armv7-unknown-linux-gnueabihf.tar.gz</a></td>
<td>ARMv7 Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-armv7-unknown-linux-gnueabihf.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-unknown-linux-musl.tar.gz">uv-aarch64-unknown-linux-musl.tar.gz</a></td>
<td>ARM64 MUSL Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-aarch64-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-unknown-linux-musl.tar.gz">uv-i686-unknown-linux-musl.tar.gz</a></td>
<td>x86 MUSL Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-i686-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-riscv64gc-unknown-linux-musl.tar.gz">uv-riscv64gc-unknown-linux-musl.tar.gz</a></td>
<td>RISCV MUSL Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-riscv64gc-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-unknown-linux-musl.tar.gz">uv-x86_64-unknown-linux-musl.tar.gz</a></td>
<td>x64 MUSL Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-x86_64-unknown-linux-musl.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-arm-unknown-linux-musleabihf.tar.gz">uv-arm-unknown-linux-musleabihf.tar.gz</a></td>
<td>ARMv6 MUSL Linux (Hardfloat)</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-arm-unknown-linux-musleabihf.tar.gz.sha256">checksum</a></td>
</tr>
<tr>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-armv7-unknown-linux-musleabihf.tar.gz">uv-armv7-unknown-linux-musleabihf.tar.gz</a></td>
<td>ARMv7 MUSL Linux</td>
<td><a
href="https://releases.astral.sh/github/uv/releases/download/0.11.6/uv-armv7-unknown-linux-musleabihf.tar.gz.sha256">checksum</a></td>
</tr>
</tbody>
</table>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.11.6</h2>
<p>Released on 2026-04-09.</p>
<p>This release resolves a low severity security advisory in which
wheels with malformed RECORD entries could delete arbitrary files on
uninstall. See <a
href="https://github.com/astral-sh/uv/security/advisories/GHSA-pjjw-68hj-v9mw">GHSA-pjjw-68hj-v9mw</a>
for details.</p>
<h3>Bug fixes</h3>
<ul>
<li>Do not remove files outside the venv on uninstall (<a
href="https://redirect.github.com/astral-sh/uv/pull/18942">#18942</a>)</li>
<li>Validate and heal wheel <code>RECORD</code> during installation (<a
href="https://redirect.github.com/astral-sh/uv/pull/18943">#18943</a>)</li>
<li>Avoid <code>uv cache clean</code> errors due to Win32 path
normalization (<a
href="https://redirect.github.com/astral-sh/uv/pull/18856">#18856</a>)</li>
</ul>
<h2>0.11.5</h2>
<p>Released on 2026-04-08.</p>
<h3>Python</h3>
<ul>
<li>Add CPython 3.13.13, 3.14.4, and 3.15.0a8 (<a
href="https://redirect.github.com/astral-sh/uv/pull/18908">#18908</a>)</li>
</ul>
<h3>Enhancements</h3>
<ul>
<li>Fix <code>build_system.requires</code> error message (<a
href="https://redirect.github.com/astral-sh/uv/pull/18911">#18911</a>)</li>
<li>Remove trailing path separators in path normalization (<a
href="https://redirect.github.com/astral-sh/uv/pull/18915">#18915</a>)</li>
<li>Improve error messages for unsupported or invalid TLS certificates
(<a
href="https://redirect.github.com/astral-sh/uv/pull/18924">#18924</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Add <code>exclude-newer</code> to <code>[[tool.uv.index]]</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/18839">#18839</a>)</li>
<li><code>uv audit</code>: add context/warnings for ignored
vulnerabilities (<a
href="https://redirect.github.com/astral-sh/uv/pull/18905">#18905</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Normalize persisted fork markers before lock equality checks (<a
href="https://redirect.github.com/astral-sh/uv/pull/18612">#18612</a>)</li>
<li>Clear junction properly when uninstalling Python versions on Windows
(<a
href="https://redirect.github.com/astral-sh/uv/pull/18815">#18815</a>)</li>
<li>Report error cleanly instead of panicking on TLS certificate error
(<a
href="https://redirect.github.com/astral-sh/uv/pull/18904">#18904</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>Remove the legacy <code>PIP_COMPATIBILITY.md</code> redirect file
(<a
href="https://redirect.github.com/astral-sh/uv/pull/18928">#18928</a>)</li>
<li>Fix <code>uv init example-bare --bare</code> examples (<a
href="https://redirect.github.com/astral-sh/uv/pull/18822">#18822</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/18925">#18925</a>)</li>
</ul>
<h2>0.11.4</h2>
<p>Released on 2026-04-07.</p>
<h3>Enhancements</h3>
<ul>
<li>Add support for <code>--upgrade-group</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/18266">#18266</a>)</li>
<li>Merge repeated archive URL hashes by version ID (<a
href="https://redirect.github.com/astral-sh/uv/pull/18841">#18841</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/65950801cc3c609b65be34938bb407ab6e30a9fe"><code>6595080</code></a>
Bump version to 0.11.6 (<a
href="https://redirect.github.com/astral-sh/uv/issues/18948">#18948</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/7983c7a5bef236fd8a04580fcedae7bd5bde4cdb"><code>7983c7a</code></a>
Validate and heal RECORD during installation (<a
href="https://redirect.github.com/astral-sh/uv/issues/18943">#18943</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/b38439bfc731d5281e933656ce2e5b910da037b0"><code>b38439b</code></a>
Avoid <code>uv cache clean</code> errors due to Win32 path normalization
(<a
href="https://redirect.github.com/astral-sh/uv/issues/18856">#18856</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a0e461ac44851f9a0f6e8974733e77d46f7a9ea9"><code>a0e461a</code></a>
Do not remove files outside the venv on uninstall (<a
href="https://redirect.github.com/astral-sh/uv/issues/18942">#18942</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/95eaa68c8df627eb915bc355831fd7d169d91fe3"><code>95eaa68</code></a>
Bump version to 0.11.5 (<a
href="https://redirect.github.com/astral-sh/uv/issues/18930">#18930</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/f6d67d57c1a9f17f7ab233654b55e061eb4bfd10"><code>f6d67d5</code></a>
Improve certificate loading error messages (<a
href="https://redirect.github.com/astral-sh/uv/issues/18924">#18924</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/39b83c30e0cdaed833e88564878376f9361987d2"><code>39b83c3</code></a>
Add <code>exclude-newer</code> to <code>[[tool.uv.index]]</code> (<a
href="https://redirect.github.com/astral-sh/uv/issues/18839">#18839</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/7924ba5b1419345dc5b9a9a16e6bcba2b59a41a6"><code>7924ba5</code></a>
uv audit: add context/warnings for ignored vulnerabilities (<a
href="https://redirect.github.com/astral-sh/uv/issues/18905">#18905</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a352ce01eab5f19bbd5929f2a5f346187552ee7c"><code>a352ce0</code></a>
Remove the legacy PIP_COMPATIBILITY.md redirect file (<a
href="https://redirect.github.com/astral-sh/uv/issues/18928">#18928</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/33b633891181f768568bfc3196039d368417fe98"><code>33b6338</code></a>
Normalize persisted fork markers before lock equality checks (<a
href="https://redirect.github.com/astral-sh/uv/issues/18612">#18612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.11.3...0.11.6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=uv&package-manager=uv&previous-version=0.11.3&new-version=0.11.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 14:28:46 -07:00
Eugene YurtsevandGitHub b442bf802a release(langgraph): 1.1.7a1 (#7476)
Adding graph life cycle callbacks
2026-04-10 21:12:03 +00:00
Eugene YurtsevandGitHub bede0b7acf test(langgraph): use monotonic clock in flaky streaming test (#7477)
Switches test_sync_streaming_with_functional_api to time.monotonic() for
both emitted task timestamps and observed arrival times so the assertion
is based on a monotonic clock instead of wall time. This makes the
streaming timing check less flaky on systems where time.time() can jump
or lack sufficient precision.

Created with [Deep Agents
CLI](https://docs.langchain.com/oss/python/deepagents/cli/overview)
using gpt-5.4 (provider: openai).
2026-04-10 21:05:37 +00:00
3a5b5c9821 feat(langgraph): add graph lifecycle callback handlers (#7429)
## Summary

This change adds first-class graph lifecycle callbacks to LangGraph so
interrupt and resume transitions can be observed without overloading the
existing LangChain custom event system. It introduces a dedicated graph
callback manager and wires lifecycle emission through Pregel execution
in both sync and async paths.

## Changes

- **`libs/langgraph/langgraph/callbacks.py`**: Adds
`GraphCallbackHandler` and `GraphCallbackManager` (built on LangChain
base callback classes), plus config plumbing via `graph_callbacks` and
`get_graph_callback_manager_for_config`.
- **`libs/langgraph/langgraph/pregel/_loop.py`**: Introduces
`GraphLifecycleEvent` and records lifecycle transitions (`resume`,
`interrupt`) into an internal FIFO queue with
`shift_graph_lifecycle_event()`.
- **`libs/langgraph/langgraph/pregel/main.py`**: Resolves graph callback
manager from config and drains lifecycle events while loop execution
progresses, dispatching `on_resume` and `on_interrupt` consistently in
sync and async runtimes.
- **`libs/langgraph/tests/test_graph_callbacks.py`**: Adds sync and
async coverage verifying lifecycle callbacks fire correctly and remain
distinct from LangChain `on_custom_event` handlers.

---------

Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2026-04-10 20:43:48 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedy
1142ebf921 chore(deps): bump langchain-core from 1.2.23 to 1.2.28 in /libs/checkpoint (#7453)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.23 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
<h2>langchain-core==1.2.27</h2>
<p>Changes since langchain-core==1.2.26</p>
<p>release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)
chore: add comment explaining <code>pygments&gt;=2.20.0</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36570">#36570</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>.</p>
<h2>langchain-core==1.2.26</h2>
<p>Changes since langchain-core==1.2.25</p>
<p>release(core): 1.2.26 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36511">#36511</a>)
fix(core): add init validator and serialization mappings for Bedrock
models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34510">#34510</a>)
feat(core): add <code>ChatBaseten</code> to serializable mapping (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36510">#36510</a>)
chore(core): drop <code>gpt-3.5-turbo</code> from docstrings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36497">#36497</a>)
fix(core): correct parameter names in filter_messages docstring example
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36462">#36462</a>)</p>
<h2>langchain-core==1.2.25</h2>
<p>Changes since langchain-core==1.2.24</p>
<p>release(core): 1.2.25 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36473">#36473</a>)
fix(core): harden check for txt files in deprecated prompt loading
functions (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue resolved in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>.</p>
<h2>langchain-core==1.2.24</h2>
<p>Changes since langchain-core==1.2.23</p>
<p>release(core): 1.2.24 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36434">#36434</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
fix(core): add &quot;computer&quot; to _WellKnownOpenAITools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36261">#36261</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/64864041168606535dfbd39055c0dca3dd61b5ba"><code>6486404</code></a>
release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7629c747260cbaed7ca55466d5b9e1b520a7de77"><code>7629c74</code></a>
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ce21bf469d7493f4716bc30feb15a5b3f16ebe1e"><code>ce21bf4</code></a>
ci: convert working-directory to validated dropdown (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36575">#36575</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8698eacbd2960c7e3195018f42992bf2c9d69c7"><code>b8698ea</code></a>
release(ollama): 1.1.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36574">#36574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3beba77e2e23d498fda07f9b8d6ba00aabfaf69f"><code>3beba77</code></a>
feat(ollama): support <code>response_format</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34612">#34612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.23...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.23&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-04-10 00:14:25 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedy
37b34bdb1e chore(deps): bump langchain-core from 1.2.22 to 1.2.28 in /libs/sdk-py (#7449)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.22 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
<h2>langchain-core==1.2.27</h2>
<p>Changes since langchain-core==1.2.26</p>
<p>release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)
chore: add comment explaining <code>pygments&gt;=2.20.0</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36570">#36570</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>.</p>
<h2>langchain-core==1.2.26</h2>
<p>Changes since langchain-core==1.2.25</p>
<p>release(core): 1.2.26 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36511">#36511</a>)
fix(core): add init validator and serialization mappings for Bedrock
models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34510">#34510</a>)
feat(core): add <code>ChatBaseten</code> to serializable mapping (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36510">#36510</a>)
chore(core): drop <code>gpt-3.5-turbo</code> from docstrings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36497">#36497</a>)
fix(core): correct parameter names in filter_messages docstring example
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36462">#36462</a>)</p>
<h2>langchain-core==1.2.25</h2>
<p>Changes since langchain-core==1.2.24</p>
<p>release(core): 1.2.25 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36473">#36473</a>)
fix(core): harden check for txt files in deprecated prompt loading
functions (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue resolved in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>.</p>
<h2>langchain-core==1.2.24</h2>
<p>Changes since langchain-core==1.2.23</p>
<p>release(core): 1.2.24 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36434">#36434</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
fix(core): add &quot;computer&quot; to _WellKnownOpenAITools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36261">#36261</a>)</p>
<h2>langchain-core==1.2.23</h2>
<p>Changes since langchain-core==1.2.22</p>
<p>release(core): 1.2.23 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36323">#36323</a>)
revert: Revert &quot;fix(core): trace invocation params in
metadata&quot; (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36322">#36322</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36243">#36243</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/64864041168606535dfbd39055c0dca3dd61b5ba"><code>6486404</code></a>
release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7629c747260cbaed7ca55466d5b9e1b520a7de77"><code>7629c74</code></a>
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ce21bf469d7493f4716bc30feb15a5b3f16ebe1e"><code>ce21bf4</code></a>
ci: convert working-directory to validated dropdown (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36575">#36575</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8698eacbd2960c7e3195018f42992bf2c9d69c7"><code>b8698ea</code></a>
release(ollama): 1.1.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36574">#36574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3beba77e2e23d498fda07f9b8d6ba00aabfaf69f"><code>3beba77</code></a>
feat(ollama): support <code>response_format</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34612">#34612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.22...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.22&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-04-09 23:55:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
11e8d827eb chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /libs/langgraph (#7457)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.6
to 46.0.7.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst">cryptography's
changelog</a>.</em></p>
<blockquote>
<p>46.0.7 - 2026-04-07</p>
<pre><code>
* **SECURITY ISSUE**: Fixed an issue where non-contiguous buffers could
be
  passed to APIs that accept Python buffers, which could lead to buffer
  overflow. **CVE-2026-39892**
* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL
3.5.6.
<p>.. _v46-0-6:<br />
</code></pre></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pyca/cryptography/commit/622d672e429a7cff836a23c5903683dbec1901f5"><code>622d672</code></a>
46.0.7 release (<a
href="https://redirect.github.com/pyca/cryptography/issues/14602">#14602</a>)</li>
<li>See full diff in <a
href="https://github.com/pyca/cryptography/compare/46.0.6...46.0.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cryptography&package-manager=uv&previous-version=46.0.6&new-version=46.0.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 23:54:47 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5fcebdd30a chore(deps): bump cryptography from 46.0.6 to 46.0.7 in /libs/cli (#7456)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.6
to 46.0.7.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst">cryptography's
changelog</a>.</em></p>
<blockquote>
<p>46.0.7 - 2026-04-07</p>
<pre><code>
* **SECURITY ISSUE**: Fixed an issue where non-contiguous buffers could
be
  passed to APIs that accept Python buffers, which could lead to buffer
  overflow. **CVE-2026-39892**
* Updated Windows, macOS, and Linux wheels to be compiled with OpenSSL
3.5.6.
<p>.. _v46-0-6:<br />
</code></pre></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pyca/cryptography/commit/622d672e429a7cff836a23c5903683dbec1901f5"><code>622d672</code></a>
46.0.7 release (<a
href="https://redirect.github.com/pyca/cryptography/issues/14602">#14602</a>)</li>
<li>See full diff in <a
href="https://github.com/pyca/cryptography/compare/46.0.6...46.0.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cryptography&package-manager=uv&previous-version=46.0.6&new-version=46.0.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 23:54:28 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
173ef2ff44 chore(deps): bump langchain-core from 1.2.22 to 1.2.28 in /libs/checkpoint-postgres (#7454)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.22 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
<h2>langchain-core==1.2.27</h2>
<p>Changes since langchain-core==1.2.26</p>
<p>release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)
chore: add comment explaining <code>pygments&gt;=2.20.0</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36570">#36570</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>.</p>
<h2>langchain-core==1.2.26</h2>
<p>Changes since langchain-core==1.2.25</p>
<p>release(core): 1.2.26 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36511">#36511</a>)
fix(core): add init validator and serialization mappings for Bedrock
models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34510">#34510</a>)
feat(core): add <code>ChatBaseten</code> to serializable mapping (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36510">#36510</a>)
chore(core): drop <code>gpt-3.5-turbo</code> from docstrings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36497">#36497</a>)
fix(core): correct parameter names in filter_messages docstring example
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36462">#36462</a>)</p>
<h2>langchain-core==1.2.25</h2>
<p>Changes since langchain-core==1.2.24</p>
<p>release(core): 1.2.25 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36473">#36473</a>)
fix(core): harden check for txt files in deprecated prompt loading
functions (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue resolved in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>.</p>
<h2>langchain-core==1.2.24</h2>
<p>Changes since langchain-core==1.2.23</p>
<p>release(core): 1.2.24 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36434">#36434</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
fix(core): add &quot;computer&quot; to _WellKnownOpenAITools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36261">#36261</a>)</p>
<h2>langchain-core==1.2.23</h2>
<p>Changes since langchain-core==1.2.22</p>
<p>release(core): 1.2.23 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36323">#36323</a>)
revert: Revert &quot;fix(core): trace invocation params in
metadata&quot; (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36322">#36322</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36243">#36243</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/64864041168606535dfbd39055c0dca3dd61b5ba"><code>6486404</code></a>
release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7629c747260cbaed7ca55466d5b9e1b520a7de77"><code>7629c74</code></a>
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ce21bf469d7493f4716bc30feb15a5b3f16ebe1e"><code>ce21bf4</code></a>
ci: convert working-directory to validated dropdown (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36575">#36575</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8698eacbd2960c7e3195018f42992bf2c9d69c7"><code>b8698ea</code></a>
release(ollama): 1.1.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36574">#36574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3beba77e2e23d498fda07f9b8d6ba00aabfaf69f"><code>3beba77</code></a>
feat(ollama): support <code>response_format</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34612">#34612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.22...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.22&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 23:54:09 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>John Kennedy
98afc106a0 chore(deps): bump the uv group across 2 directories with 1 update (#7458)
Bumps the uv group with 1 update in the /libs/cli/uv-examples/monorepo
directory: [langchain-core](https://github.com/langchain-ai/langchain).
Bumps the uv group with 1 update in the /libs/cli/uv-examples/simple
directory: [langchain-core](https://github.com/langchain-ai/langchain).

Updates `langchain-core` from 1.2.27 to 1.2.28
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li>See full diff in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.27...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />

Updates `langchain-core` from 1.2.27 to 1.2.28
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li>See full diff in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.27...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
2026-04-09 23:51:22 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
80ef3ced0b chore(deps): bump langchain-core from 1.2.22 to 1.2.28 in /libs/checkpoint-conformance (#7448)
Bumps [langchain-core](https://github.com/langchain-ai/langchain) from
1.2.22 to 1.2.28.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/langchain-ai/langchain/releases">langchain-core's
releases</a>.</em></p>
<blockquote>
<h2>langchain-core==1.2.28</h2>
<p>Changes since langchain-core==1.2.27</p>
<p>release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</p>
<h2>langchain-core==1.2.27</h2>
<p>Changes since langchain-core==1.2.26</p>
<p>release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)
chore: add comment explaining <code>pygments&gt;=2.20.0</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36570">#36570</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>.</p>
<h2>langchain-core==1.2.26</h2>
<p>Changes since langchain-core==1.2.25</p>
<p>release(core): 1.2.26 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36511">#36511</a>)
fix(core): add init validator and serialization mappings for Bedrock
models (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34510">#34510</a>)
feat(core): add <code>ChatBaseten</code> to serializable mapping (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36510">#36510</a>)
chore(core): drop <code>gpt-3.5-turbo</code> from docstrings (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36497">#36497</a>)
fix(core): correct parameter names in filter_messages docstring example
(<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36462">#36462</a>)</p>
<h2>langchain-core==1.2.25</h2>
<p>Changes since langchain-core==1.2.24</p>
<p>release(core): 1.2.25 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36473">#36473</a>)
fix(core): harden check for txt files in deprecated prompt loading
functions (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>)
fix(core): fixed typos in the documentation (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36459">#36459</a>)</p>
<p>Credit to Jeff Ponte (<a
href="https://github.com/JDP-Security"><code>@​JDP-Security</code></a>)
for reporting the symlink resolution issue resolved in <a
href="https://redirect.github.com/langchain-ai/langchain/issues/36471">#36471</a>.</p>
<h2>langchain-core==1.2.24</h2>
<p>Changes since langchain-core==1.2.23</p>
<p>release(core): 1.2.24 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36434">#36434</a>)
feat(core): impute placeholder filenames for OpenAI file inputs (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36433">#36433</a>)
chore: pygments&gt;=2.20.0 across all packages (CVE-2026-4539) (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36385">#36385</a>)
fix(core): add &quot;computer&quot; to _WellKnownOpenAITools (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36261">#36261</a>)</p>
<h2>langchain-core==1.2.23</h2>
<p>Changes since langchain-core==1.2.22</p>
<p>release(core): 1.2.23 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36323">#36323</a>)
revert: Revert &quot;fix(core): trace invocation params in
metadata&quot; (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36322">#36322</a>)
chore: bump requests from 2.32.5 to 2.33.0 in /libs/core (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36243">#36243</a>)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/langchain-ai/langchain/commit/dd7c3eb3a4acfc834b038ec9dbde94478c66776e"><code>dd7c3eb</code></a>
release(core): release 1.2.28 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36614">#36614</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/af2ed47c6f008cdd551f3c0d87db3774c8dfe258"><code>af2ed47</code></a>
fix(core): add more sanitization to templates (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36612">#36612</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7e5858d8078124f98f10102da21414689467c132"><code>7e5858d</code></a>
release(standard-tests): 1.1.6 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36610">#36610</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/fe99cb29123b704a90f5c8587a757def3b1471e0"><code>fe99cb2</code></a>
fix(standard-tests): update standard tests for sandbox backends (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36036">#36036</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/65bbd47cb2721c51ef8638f9e7da35247c4bfdde"><code>65bbd47</code></a>
chore(model-profiles): refresh model profile data (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36596">#36596</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/64864041168606535dfbd39055c0dca3dd61b5ba"><code>6486404</code></a>
release(core): 1.2.27 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36586">#36586</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/7629c747260cbaed7ca55466d5b9e1b520a7de77"><code>7629c74</code></a>
fix(core): handle symlinks in deprecated prompt save path (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36585">#36585</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/ce21bf469d7493f4716bc30feb15a5b3f16ebe1e"><code>ce21bf4</code></a>
ci: convert working-directory to validated dropdown (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36575">#36575</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/b8698eacbd2960c7e3195018f42992bf2c9d69c7"><code>b8698ea</code></a>
release(ollama): 1.1.0 (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/36574">#36574</a>)</li>
<li><a
href="https://github.com/langchain-ai/langchain/commit/3beba77e2e23d498fda07f9b8d6ba00aabfaf69f"><code>3beba77</code></a>
feat(ollama): support <code>response_format</code> (<a
href="https://redirect.github.com/langchain-ai/langchain/issues/34612">#34612</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/langchain-ai/langchain/compare/langchain-core==1.2.22...langchain-core==1.2.28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=langchain-core&package-manager=uv&previous-version=1.2.22&new-version=1.2.28)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/langchain-ai/langgraph/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 16:46:02 -07:00
1629794658 chore: update conformance lint (#7459)
Co-authored-by: Will Fu-Hinthorn <will@langchain.dev>
2026-04-08 18:22:18 -07:00
127 changed files with 24011 additions and 2190 deletions
+2 -2
View File
@@ -121,8 +121,8 @@ jobs:
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.0.1" ]; then
echo "LANGCHAIN_OPENAI_VERSION != 1.0.1; $LANGCHAIN_OPENAI_VERSION"
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);")
+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@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # 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@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
path: ${{ inputs.working-directory }}/dist/
- name: Publish to test PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
packages-dir: ${{ inputs.working-directory }}/dist/
verbose: true
+1 -1
View File
@@ -57,7 +57,7 @@ jobs:
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Annotation
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const file = JSON.parse(`${{ steps.files.outputs.added_modified_renamed }}`)[0]
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
uses: actions/configure-pages@v6
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v5
with:
path: 'docs/_site'
+1 -1
View File
@@ -276,7 +276,7 @@ jobs:
path: ${{ inputs.working-directory }}/dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
packages-dir: ${{ inputs.working-directory }}/dist/
verbose: true
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
steps:
- name: Find and reopen matching PRs
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
+4 -4
View File
@@ -50,7 +50,7 @@ jobs:
steps:
- name: Check for issue link and assignee
id: check-link
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
@@ -283,7 +283,7 @@ jobs:
if: >-
env.ENFORCE_ISSUE_LINK == 'true' &&
(steps.check-link.outputs.has-link != 'true' || steps.check-link.outputs.is-assigned != 'true')
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
@@ -312,7 +312,7 @@ jobs:
if: >-
env.ENFORCE_ISSUE_LINK == 'true' &&
steps.check-link.outputs.has-link == 'true' && steps.check-link.outputs.is-assigned == 'true'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
@@ -366,7 +366,7 @@ jobs:
if: >-
env.ENFORCE_ISSUE_LINK == 'true' &&
(steps.check-link.outputs.has-link != 'true' || steps.check-link.outputs.is-assigned != 'true')
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
+5 -5
View File
@@ -63,7 +63,7 @@ jobs:
- name: Check if contributor is external
if: steps.app-token.outcome == 'success'
id: check-membership
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
@@ -102,7 +102,7 @@ jobs:
- name: Apply contributor tier label
if: steps.check-membership.outputs.is-external == 'true'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -155,7 +155,7 @@ jobs:
- name: Add external label
if: steps.check-membership.outputs.is-external == 'true'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -168,7 +168,7 @@ jobs:
- name: Add internal label
if: steps.check-membership.outputs.is-external == 'false'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -196,7 +196,7 @@ jobs:
private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
- name: Backfill labels
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
+4 -4
View File
@@ -48,7 +48,7 @@ jobs:
- name: Check if contributor is external
if: steps.app-token.outcome == 'success'
id: check-membership
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
@@ -90,7 +90,7 @@ jobs:
# event fires and triggers require_issue_link.yml.
- name: Apply contributor tier label
if: steps.check-membership.outputs.is-external == 'true'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
# Use App token so the "labeled" event propagates to downstream
# workflows (e.g. require_issue_link.yml).
@@ -145,7 +145,7 @@ jobs:
- name: Add external label
if: steps.check-membership.outputs.is-external == 'true'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
# Use App token so the "labeled" event propagates to downstream
# workflows (e.g. require_issue_link.yml). Events created by the
@@ -161,7 +161,7 @@ jobs:
- name: Add internal label
if: steps.check-membership.outputs.is-external == 'false'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
run: make lock-upgrade
- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(deps): upgrade dependencies with `uv lock --upgrade`"
+1
View File
@@ -100,3 +100,4 @@ dmypy.json
.turbo
.editorconfig
.scratch
.worktrees/
+1 -1
View File
@@ -16,7 +16,7 @@
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/pypi/l/langgraph" alt="PyPI - License"></a>
<a href="https://pypistats.org/packages/langgraph" target="_blank"><img src="https://img.shields.io/pepy/dt/langgraph" alt="PyPI - Downloads"></a>
<a href="https://pypi.org/project/langgraph/" target="_blank"><img src="https://img.shields.io/pypi/v/langgraph.svg?label=%20" alt="Version"></a>
<a href="https://x.com/langchain" target="_blank"><img src="https://img.shields.io/twitter/url/https/twitter.com/langchain.svg?style=social&label=Follow%20%40LangChain" alt="Twitter / X"></a>
<a href="https://x.com/langchain_oss" target="_blank"><img src="https://img.shields.io/twitter/url/https/twitter.com/langchain_oss.svg?style=social&label=Follow%20%40LangChain" alt="Twitter / X"></a>
</div>
<br>
@@ -339,7 +339,6 @@ async def test_list_metadata_custom_keys(
assert results[0].metadata["run_id"] == "run-abc"
ALL_LIST_TESTS = [
test_list_all,
test_list_by_thread,
+50 -50
View File
@@ -231,7 +231,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.22"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -243,27 +243,27 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "ormsgpack" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7c/e1/885e49cdafceb4c74dae4573bc5dd6054c6c640382ee73104532f33dca46/langgraph_checkpoint-4.0.3.tar.gz", hash = "sha256:a7b5e2ca18fb79b55edf19396d4ee446f8a53dcb7a4ec62ce6f1c7e00bb5af7f", size = 174009, upload-time = "2026-04-27T14:34:02.777Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" },
{ url = "https://files.pythonhosted.org/packages/19/ee/ecd3fa2e893746dde3b768daca2a4935208bc77d09445437ccfffb4a8c9b/langgraph_checkpoint-4.0.3-py3-none-any.whl", hash = "sha256:b91b765712a2311a5b198760f714b7ab9b376d01c047ed78d9b9a3e80df802a3", size = 51682, upload-time = "2026-04-27T14:34:01.51Z" },
]
[[package]]
name = "langgraph-checkpoint-conformance"
version = "0.0.1"
version = "0.0.2"
source = { editable = "." }
dependencies = [
{ name = "langgraph-checkpoint" },
@@ -306,7 +306,7 @@ test = [
[[package]]
name = "langsmith"
version = "0.7.3"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -319,9 +319,9 @@ dependencies = [
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8d/bc/8172fefad4f2da888a6d564a27d1fb7d4dbf3c640899c2b40c46235cbe98/langsmith-0.7.3.tar.gz", hash = "sha256:0223b97021af62d2cf53c8a378a27bd22e90a7327e45b353e0069ae60d5d6f9e", size = 988575, upload-time = "2026-02-13T23:25:32.916Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/9d/5a68b6b5e313ffabbb9725d18a71edb48177fd6d3ad329c07801d2a8e862/langsmith-0.7.3-py3-none-any.whl", hash = "sha256:03659bf9274e6efcead361c9c31a7849ea565ae0d6c0d73e1d8b239029eff3be", size = 325718, upload-time = "2026-02-13T23:25:31.52Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
@@ -623,7 +623,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.0.2"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -634,9 +634,9 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
@@ -746,27 +746,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.8"
version = "0.15.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" }
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" },
{ url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" },
{ url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" },
{ url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" },
{ url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" },
{ url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" },
{ url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" },
{ url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" },
{ url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" },
{ url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" },
{ url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" },
{ url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" },
{ url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" },
{ url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" },
{ url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" },
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
]
[[package]]
@@ -834,26 +834,26 @@ wheels = [
[[package]]
name = "ty"
version = "0.0.27"
version = "0.0.33"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f4/de/e5cf1f151cf52fe1189e42d03d90909d7d1354fdc0c1847cbb63a0baa3da/ty-0.0.27.tar.gz", hash = "sha256:d7a8de3421d92420b40c94fe7e7d4816037560621903964dd035cf9bd0204a73", size = 5424130, upload-time = "2026-03-31T19:07:20.806Z" }
sdist = { url = "https://files.pythonhosted.org/packages/84/44/9478c50c266826c1bf30d1692e589755bffa8f1c0a3eb7af8a346c255991/ty-0.0.33.tar.gz", hash = "sha256:46d63bda07403322cb6c28ccfdd5536be916e13df725c29f7ccd0a21f06bd9e8", size = 5559373, upload-time = "2026-04-28T10:45:13.18Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/20/2a9ea661758bd67f2bfd54ce9daacb5a26c56c5f8b49fbd9a43b365a8a7d/ty-0.0.27-py3-none-linux_armv6l.whl", hash = "sha256:eb14456b8611c9e8287aa9b633f4d2a0d9f3082a31796969e0b50bdda8930281", size = 10571211, upload-time = "2026-03-31T19:07:23.28Z" },
{ url = "https://files.pythonhosted.org/packages/da/b2/8887a51f705d075ddbe78ae7f0d4755ef48d0a90235f67aee289e9cee950/ty-0.0.27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:02e662184703db7586118df611cf24a000d35dae38d950053d1dd7b6736fd2c4", size = 10427576, upload-time = "2026-03-31T19:07:15.499Z" },
{ url = "https://files.pythonhosted.org/packages/1d/c3/79d88163f508fb709ce19bc0b0a66c7c64b53d372d4caa56172c3d9b3ae8/ty-0.0.27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:be5fc2899441f7f8f7ef40f9ffd006075a5ff6b06c44e8d2aa30e1b900c12f51", size = 9870359, upload-time = "2026-03-31T19:07:36.852Z" },
{ url = "https://files.pythonhosted.org/packages/dc/4d/ed1b0db0e1e46b5ed4976bbfe0d1825faf003b4e3774ef28c785ed73e4bb/ty-0.0.27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30231e652b14742a76b64755e54bf0cb1cd4c128bcaf625222e0ca92a2094887", size = 10380488, upload-time = "2026-03-31T19:07:31.268Z" },
{ url = "https://files.pythonhosted.org/packages/b1/f2/20372f6d510b01570028433064880adec2f8abe68bf0c4603be61a560bef/ty-0.0.27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a119b1168f64261b3205a37e40b5b6c4aac8fd58e4587988f4e4b22c3c79847", size = 10390248, upload-time = "2026-03-31T19:07:28.345Z" },
{ url = "https://files.pythonhosted.org/packages/45/4b/46b31a7311306be1a560f7f20fdc37b5bf718787f60626cd265d9b637554/ty-0.0.27-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e38f4e187b6975d2cbebf0f1eb1221f8f64f6e509bad14d7bb2a91afc97e4956", size = 10878479, upload-time = "2026-03-31T19:07:39.393Z" },
{ url = "https://files.pythonhosted.org/packages/42/ba/5231a2a1fb1cebe053a25de8fded95e1a30a1e77d3628a9e58487297bafc/ty-0.0.27-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a07b1a8fbb23844f6d22091275430d9ac617175f34aa99159b268193de210389", size = 11461232, upload-time = "2026-03-31T19:07:02.518Z" },
{ url = "https://files.pythonhosted.org/packages/c3/37/558abab3e1f6670493524f61280b4dfcc3219555f13889223e733381dfab/ty-0.0.27-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3ec4033031f240836bb0337274bac5c49dde312c7c6d7575451ed719bf8ffa3", size = 11133002, upload-time = "2026-03-31T19:07:18.371Z" },
{ url = "https://files.pythonhosted.org/packages/32/38/188c14a57f52160407ce62c6abb556011718fd0bcbe1dca690529ce84c46/ty-0.0.27-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:924a8849afd500d260bf5b7296165a05b7424fbb6b19113f30f3b999d682873f", size = 10986624, upload-time = "2026-03-31T19:07:13.066Z" },
{ url = "https://files.pythonhosted.org/packages/9f/f1/667a71393f47d2cd6ba9ed07541b8df3eb63aab1f2ee658e77d91b8362fa/ty-0.0.27-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d8270026c07e7423a1b3a3fd065b46ed1478748f0662518b523b57744f3fa025", size = 10366721, upload-time = "2026-03-31T19:07:00.131Z" },
{ url = "https://files.pythonhosted.org/packages/8b/aa/8edafe41be898bda774249abc5be6edd733e53fb1777d59ea9331e38537d/ty-0.0.27-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e26e9735d3bdfd95d881111ad1cf570eab8188d8c3be36d6bcaad044d38984d8", size = 10412239, upload-time = "2026-03-31T19:07:05.297Z" },
{ url = "https://files.pythonhosted.org/packages/53/ff/8bafaed4a18d38264f46bdfc427de7ea2974cf9064e4e0bdb1b6e6c724e3/ty-0.0.27-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7c09cc9a699810609acc0090af8d0db68adaee6e60a7c3e05ab80cc954a83db7", size = 10573507, upload-time = "2026-03-31T19:06:57.064Z" },
{ url = "https://files.pythonhosted.org/packages/16/2e/63a8284a2fefd08ab56ecbad0fde7dd4b2d4045a31cf24c1d1fcd9643227/ty-0.0.27-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2d3e02853bb037221a456e034b1898aaa573e6374fbb53884e33cb7513ccb85a", size = 11090233, upload-time = "2026-03-31T19:07:34.139Z" },
{ url = "https://files.pythonhosted.org/packages/14/d3/d6fa1cafdfa2b34dbfa304fc6833af8e1669fc34e24d214fa76d2a2e5a25/ty-0.0.27-py3-none-win32.whl", hash = "sha256:34e7377f2047c14dbbb7bf5322e84114db7a5f2cb470db6bee63f8f3550cfc1e", size = 9984415, upload-time = "2026-03-31T19:07:07.98Z" },
{ url = "https://files.pythonhosted.org/packages/85/e6/dd4e27da9632b3472d5711ca49dbd3709dbd3e8c73f3af6db9c254235ca9/ty-0.0.27-py3-none-win_amd64.whl", hash = "sha256:3f7e4145aad8b815ed69b324c93b5b773eb864dda366ca16ab8693ff88ce6f36", size = 10961535, upload-time = "2026-03-31T19:07:10.566Z" },
{ url = "https://files.pythonhosted.org/packages/0e/1a/824b3496d66852ed7d5d68d9787711131552b68dce8835ce9410db32e618/ty-0.0.27-py3-none-win_arm64.whl", hash = "sha256:95bf8d01eb96bb2ba3ffc39faff19da595176448e80871a7b362f4d2de58476c", size = 10376689, upload-time = "2026-03-31T19:07:25.732Z" },
{ url = "https://files.pythonhosted.org/packages/e9/24/e287388c63a19191be26b32ff4dbd06029834068150ebe2532939bc4c851/ty-0.0.33-py3-none-linux_armv6l.whl", hash = "sha256:94d0a9d2234261a8911396d59e506b5923fe0971dbda43b9dcea287936887fcc", size = 11021308, upload-time = "2026-04-28T10:45:43.34Z" },
{ url = "https://files.pythonhosted.org/packages/00/ca/ba1eed819895bd239fba8ee35dfcd5fcb266c203b0914a17a59579096bb5/ty-0.0.33-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4a2b5ba078f90de342f56b5f7979bb77c9b9b1d8625a041352ffc6ee93c4073", size = 10777272, upload-time = "2026-04-28T10:45:32.905Z" },
{ url = "https://files.pythonhosted.org/packages/25/a8/c3131d37b44b3fea1d6654a1c929a0cd0873822f77a90482b8ec28f6fbbd/ty-0.0.33-py3-none-macosx_11_0_arm64.whl", hash = "sha256:84ff5707825e9af9668d2bcf66975f93e520a63b524ab494e3a8265735be2563", size = 10201078, upload-time = "2026-04-28T10:45:23.374Z" },
{ url = "https://files.pythonhosted.org/packages/7b/db/d8e37ff0045810cc65e1ff36aa0da0a2253c05659787ac987df8a16c7897/ty-0.0.33-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e375285736f57886868e7af0b11c7b0ec5b6543fa15e7ad2a714fed9f077d4e0", size = 10732347, upload-time = "2026-04-28T10:45:21.444Z" },
{ url = "https://files.pythonhosted.org/packages/e0/1a/20e83a412506a918e4684fc67b567cf7cc13b105470b3428cb23c3d5aa13/ty-0.0.33-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5680f6350c3b4e46b8bff6d7bb132366ea239463d6cad4892725d06046e65464", size = 10808238, upload-time = "2026-04-28T10:45:38.565Z" },
{ url = "https://files.pythonhosted.org/packages/5d/4b/d0a39f4464dc6cb4cc2c159473ce216bd1846bfb684c0323a3cb36dce5c6/ty-0.0.33-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5535538bad8d0f7e62bcdff02197cdb30e41451d80b35d27e17d128f2e1dc5d", size = 11288348, upload-time = "2026-04-28T10:45:08.419Z" },
{ url = "https://files.pythonhosted.org/packages/35/7e/f1745e0f9583363d7a83d9a4990fc244f76ecc30840ddad83dc16a33c52d/ty-0.0.33-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da196c42bbbc069e1e21e3e52107c061aa9660352dae57a41930690b56e2c02d", size = 11789907, upload-time = "2026-04-28T10:45:19.064Z" },
{ url = "https://files.pythonhosted.org/packages/a5/71/25f39f46a12d662859d45bc648555d0661044eb43db6b5648c9947487da9/ty-0.0.33-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9281672921ef6d4460e03146b5e6c18cb1a3e3a3b8a1a88f6f33226d05a469b7", size = 11500774, upload-time = "2026-04-28T10:45:48.012Z" },
{ url = "https://files.pythonhosted.org/packages/94/ec/136959ecbb7c71cb90537f5aea441c73f4ab24612868a6ecdc9d7444d32d/ty-0.0.33-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82c1b8f303f82da64e878108e764be3ecbcd7c9903ac0a7f7031614ed00b97ab", size = 11360314, upload-time = "2026-04-28T10:45:05.402Z" },
{ url = "https://files.pythonhosted.org/packages/cf/95/32809575c222f00beed498cb728e9290a0f5009f930025381bb7253b2206/ty-0.0.33-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:efe3af412c9ff67bce5fa37d0a2b0d8555c24072b145a5bac6c79637f1c83abe", size = 10707785, upload-time = "2026-04-28T10:45:10.836Z" },
{ url = "https://files.pythonhosted.org/packages/13/89/c8e9531f7aa4a093359e15fa32c8e1277fbbe90d16894d7c6032d29f4b34/ty-0.0.33-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aeec29c91ea768601747da546c3efc20b72c2fb1bd52bcc786a5c6eeff51d27b", size = 10834987, upload-time = "2026-04-28T10:45:40.738Z" },
{ url = "https://files.pythonhosted.org/packages/31/16/9835fbcf5338af1a1917bd28fdb8a7193c210b83f243aa286fa9f79cb3ad/ty-0.0.33-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a535977c52bbb5f7e96b8b70a6ad375ad077f4a9ff2492508ea3816a2b403819", size = 10968968, upload-time = "2026-04-28T10:45:30.26Z" },
{ url = "https://files.pythonhosted.org/packages/36/69/64c76aabc1bc70c7f24b686cd93c3407f8ea430905e395f59bf9603ef571/ty-0.0.33-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1d732facf39fcb221ba279d469c5040d37883e964f123b1563888efd34818180", size = 11458077, upload-time = "2026-04-28T10:45:45.971Z" },
{ url = "https://files.pythonhosted.org/packages/91/84/fae27b0c4718776a298690d31ca4cc1995f2e3e1c63a7b59e84c41498e9a/ty-0.0.33-py3-none-win32.whl", hash = "sha256:d90960b574428dc252f85e8598ec5fcb7f619794196b2fc95a90da075ed4681c", size = 10345364, upload-time = "2026-04-28T10:45:16.836Z" },
{ url = "https://files.pythonhosted.org/packages/3c/a0/a2938b23ae3e1a09a2d7c189e2ac5f7113676bae4e0e23948b568e18e5f8/ty-0.0.33-py3-none-win_amd64.whl", hash = "sha256:c1c3aec62c44de610c6e95f0a4e97ac3dbc07934bfdbf1fd90d758c9ff72f48e", size = 11342470, upload-time = "2026-04-28T10:45:26.455Z" },
{ url = "https://files.pythonhosted.org/packages/ab/62/7fb948aace38d2f6329261bb33c035a8484549c74f1db28649c7a4c6fed9/ty-0.0.33-py3-none-win_arm64.whl", hash = "sha256:0d44f99ba1b441e55e2aa301b2ac0a21112784931b46a5f66f4ea9efe5620d97", size = 10742673, upload-time = "2026-04-28T10:45:35.555Z" },
]
[[package]]
+5
View File
@@ -6,6 +6,11 @@ Implementation of LangGraph CheckpointSaver that uses Postgres.
By default `langgraph-checkpoint-postgres` installs `psycopg` (Psycopg 3) without any extras. However, you can choose a specific installation that best suits your needs [here](https://www.psycopg.org/psycopg3/docs/basic/install.html) (for example, `psycopg[binary]`).
## Security
> [!IMPORTANT]
> Set `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list when creating your checkpointer. This restricts checkpoint deserialization to known-safe types, preventing code execution if the database is compromised. See the [langgraph-checkpoint README](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint#serde) for details.
## Usage
> [!IMPORTANT]
@@ -4,26 +4,35 @@ import threading
from collections import defaultdict
from collections.abc import Iterator, Sequence
from contextlib import contextmanager
from typing import Any
from typing import Any, cast
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
WRITES_IDX_MAP,
ChannelVersions,
Checkpoint,
CheckpointMetadata,
CheckpointTuple,
_ChannelWritesHistory,
get_checkpoint_id,
get_serializable_checkpoint_metadata,
)
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.types import _DeltaSnapshot
from psycopg import Capabilities, Connection, Cursor, Pipeline
from psycopg.rows import DictRow, dict_row
from psycopg.types.json import Jsonb
from psycopg_pool import ConnectionPool
from langgraph.checkpoint.postgres import _internal
from langgraph.checkpoint.postgres.base import BasePostgresSaver
from langgraph.checkpoint.postgres.base import (
SELECT_DELTA_STAGE1_SQL,
SELECT_DELTA_STAGE2_SQL,
BasePostgresSaver,
_DeltaStage1Row,
_DeltaStage2Row,
)
from langgraph.checkpoint.postgres.shallow import ShallowPostgresSaver
Conn = _internal.Conn # For backward compatibility
@@ -302,7 +311,12 @@ class PostgresSaver(BasePostgresSaver):
# others are stored in blobs table
blob_values = {}
for k, v in checkpoint["channel_values"].items():
if v is None or isinstance(v, (str, int, float, bool)):
if v is DELTA_SENTINEL:
copy["channel_values"].pop(k)
elif isinstance(v, _DeltaSnapshot):
blob_values[k] = copy["channel_values"].pop(k)
copy["channel_values"][k] = True
elif v is None or isinstance(v, (str, int, float, bool)):
pass
else:
blob_values[k] = copy["channel_values"].pop(k)
@@ -430,6 +444,56 @@ class PostgresSaver(BasePostgresSaver):
with conn.cursor(binary=True, row_factory=dict_row) as cur:
yield cur
def _get_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
"""Fast-path override of `BaseCheckpointSaver._get_channel_writes_history`.
Two-stage query: stage 1 scans checkpoint metadata to walk the parent
chain and locate the nearest snapshot; stage 2 fetches only the
chain-limited writes and single seed blob.
"""
thread_id = config["configurable"]["thread_id"]
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
checkpoint_id = get_checkpoint_id(config)
if checkpoint_id is None:
target = self.get_tuple(config)
if target is None:
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=[])
checkpoint_id = target.config["configurable"]["checkpoint_id"]
with self._cursor() as cur:
cur.execute(
SELECT_DELTA_STAGE1_SQL,
(channel, channel, thread_id, checkpoint_ns),
)
stage1_rows = cur.fetchall()
chain_cids, seed_version = self._walk_stage1(
cast("list[_DeltaStage1Row]", stage1_rows), checkpoint_id
)
seed_versions = [seed_version] if seed_version else []
with self._cursor() as cur:
cur.execute(
SELECT_DELTA_STAGE2_SQL,
(
thread_id,
checkpoint_ns,
channel,
chain_cids,
thread_id,
checkpoint_ns,
channel,
seed_versions,
),
)
stage2_rows = cur.fetchall()
return self._build_delta_channel_writes_history(
channel=channel,
chain_cids=chain_cids,
seed_version=seed_version,
stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows),
)
def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
"""
Convert a database row into a CheckpointTuple object.
@@ -4,26 +4,35 @@ import asyncio
from collections import defaultdict
from collections.abc import AsyncIterator, Iterator, Sequence
from contextlib import asynccontextmanager
from typing import Any
from typing import Any, cast
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
WRITES_IDX_MAP,
ChannelVersions,
Checkpoint,
CheckpointMetadata,
CheckpointTuple,
_ChannelWritesHistory,
get_checkpoint_id,
get_serializable_checkpoint_metadata,
)
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.types import _DeltaSnapshot
from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities
from psycopg.rows import DictRow, dict_row
from psycopg.types.json import Jsonb
from psycopg_pool import AsyncConnectionPool
from langgraph.checkpoint.postgres import _ainternal
from langgraph.checkpoint.postgres.base import BasePostgresSaver
from langgraph.checkpoint.postgres.base import (
SELECT_DELTA_STAGE1_SQL,
SELECT_DELTA_STAGE2_SQL,
BasePostgresSaver,
_DeltaStage1Row,
_DeltaStage2Row,
)
from langgraph.checkpoint.postgres.shallow import AsyncShallowPostgresSaver
Conn = _ainternal.Conn # For backward compatibility
@@ -261,7 +270,12 @@ class AsyncPostgresSaver(BasePostgresSaver):
# others are stored in blobs table
blob_values = {}
for k, v in checkpoint["channel_values"].items():
if v is None or isinstance(v, (str, int, float, bool)):
if v is DELTA_SENTINEL:
copy["channel_values"].pop(k)
elif isinstance(v, _DeltaSnapshot):
blob_values[k] = copy["channel_values"].pop(k)
copy["channel_values"][k] = True
elif v is None or isinstance(v, (str, int, float, bool)):
pass
else:
blob_values[k] = copy["channel_values"].pop(k)
@@ -391,6 +405,56 @@ class AsyncPostgresSaver(BasePostgresSaver):
async with conn.cursor(binary=True, row_factory=dict_row) as cur:
yield cur
async def _aget_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
"""Fast-path override of `BaseCheckpointSaver._aget_channel_writes_history`.
Two-stage query: stage 1 scans checkpoint metadata to walk the parent
chain and locate the nearest snapshot; stage 2 fetches only the
chain-limited writes and single seed blob.
"""
thread_id = config["configurable"]["thread_id"]
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
checkpoint_id = get_checkpoint_id(config)
if checkpoint_id is None:
target = await self.aget_tuple(config)
if target is None:
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=[])
checkpoint_id = target.config["configurable"]["checkpoint_id"]
async with self._cursor() as cur:
await cur.execute(
SELECT_DELTA_STAGE1_SQL,
(channel, channel, thread_id, checkpoint_ns),
)
stage1_rows = await cur.fetchall()
chain_cids, seed_version = self._walk_stage1(
cast("list[_DeltaStage1Row]", stage1_rows), checkpoint_id
)
seed_versions = [seed_version] if seed_version else []
async with self._cursor() as cur:
await cur.execute(
SELECT_DELTA_STAGE2_SQL,
(
thread_id,
checkpoint_ns,
channel,
chain_cids,
thread_id,
checkpoint_ns,
channel,
seed_versions,
),
)
stage2_rows = await cur.fetchall()
return self._build_delta_channel_writes_history(
channel=channel,
chain_cids=chain_cids,
seed_version=seed_version,
stage2_rows=cast("list[_DeltaStage2Row]", stage2_rows),
)
async def _load_checkpoint_tuple(self, value: DictRow) -> CheckpointTuple:
"""
Convert a database row into a CheckpointTuple object.
@@ -4,13 +4,16 @@ import random
import warnings
from collections.abc import Sequence
from importlib.metadata import version as get_version
from typing import Any, cast
from typing import Any, TypedDict, cast
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
WRITES_IDX_MAP,
BaseCheckpointSaver,
ChannelVersions,
PendingWrite,
_ChannelWritesHistory,
get_checkpoint_id,
)
from langgraph.checkpoint.serde.types import TASKS
@@ -153,6 +156,62 @@ INSERT_CHECKPOINT_WRITES_SQL = """
"""
class _DeltaStage2Row(TypedDict, total=False):
"""One row from `SELECT_DELTA_STAGE2_SQL` (a UNION ALL of writes and blobs)."""
_kind: str # "w" or "b"
checkpoint_id: str | None # "w" rows only
type: str | None
blob: bytes | None
task_id: str | None # "w" rows only
idx: int | None # "w" rows only
version: str | None # "b" rows only
# Two-stage DeltaChannel reconstruction. Stage 1 scans checkpoint
# metadata (no blob bytes) to walk the parent chain and locate the
# nearest snapshot marker. Stage 2 fetches only the chain-limited
# writes and the single seed snapshot blob.
#
# Parameter order:
# stage1: (channel, channel, thread_id, checkpoint_ns)
# stage2: (thread_id, checkpoint_ns, channel, chain_cids[],
# thread_id, checkpoint_ns, channel, seed_versions[])
SELECT_DELTA_STAGE1_SQL = """
SELECT checkpoint_id,
parent_checkpoint_id,
checkpoint -> 'channel_versions' ->> %s AS ver,
(checkpoint -> 'channel_values' -> %s) IS NOT NULL AS has_snapshot
FROM checkpoints
WHERE thread_id = %s AND checkpoint_ns = %s
"""
SELECT_DELTA_STAGE2_SQL = """
SELECT 'w'::text AS _kind,
checkpoint_id,
type, blob, task_id, idx, NULL::text AS version
FROM checkpoint_writes
WHERE thread_id = %s AND checkpoint_ns = %s AND channel = %s
AND checkpoint_id = ANY(%s)
UNION ALL
SELECT 'b', NULL,
type, blob, NULL, NULL, version
FROM checkpoint_blobs
WHERE thread_id = %s AND checkpoint_ns = %s AND channel = %s
AND version = ANY(%s)
"""
class _DeltaStage1Row(TypedDict):
"""One row from `SELECT_DELTA_STAGE1_SQL`."""
checkpoint_id: str
parent_checkpoint_id: str | None
ver: str | None
has_snapshot: bool
class BasePostgresSaver(BaseCheckpointSaver[str]):
SELECT_SQL = SELECT_SQL
SELECT_PENDING_SENDS_SQL = SELECT_PENDING_SENDS_SQL
@@ -195,6 +254,88 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
if t.decode() != "empty"
}
@staticmethod
def _walk_stage1(
stage1_rows: Sequence[_DeltaStage1Row],
target_id: str,
) -> tuple[list[str], str | None]:
"""Walk the parent chain from stage 1 metadata rows.
Returns (chain_cids, seed_version):
chain_cids: ancestor checkpoint IDs from target's parent down to
the seed (or root), in newest-first order.
seed_version: the channel blob version at the nearest ancestor
with has_snapshot=True, or None if pure delta.
"""
parent_of: dict[str, str | None] = {}
ver_of: dict[str, str | None] = {}
snapshot_of: dict[str, bool] = {}
for r in stage1_rows:
cid = r["checkpoint_id"]
parent_of[cid] = r["parent_checkpoint_id"]
ver_of[cid] = r["ver"]
snapshot_of[cid] = r["has_snapshot"]
chain_cids: list[str] = []
seed_version: str | None = None
cur_cid: str | None = parent_of.get(target_id)
while cur_cid is not None:
chain_cids.append(cur_cid)
if snapshot_of.get(cur_cid, False):
seed_version = ver_of.get(cur_cid)
break
cur_cid = parent_of.get(cur_cid)
return chain_cids, seed_version
def _build_delta_channel_writes_history(
self,
*,
channel: str,
chain_cids: list[str],
seed_version: str | None,
stage2_rows: Sequence[_DeltaStage2Row],
) -> _ChannelWritesHistory:
"""Reconstruct delta channel history from two-stage query results.
chain_cids are in newest-first order (target's parent first).
stage2_rows contain only writes for chain_cids and the single
seed blob at seed_version.
"""
writes_by_cid: dict[str, list[tuple[str, bytes, str, int]]] = {}
seed_blob: tuple[str, bytes] | None = None
for r in stage2_rows:
kind = r["_kind"]
if kind == "w":
cid = cast(str, r["checkpoint_id"])
writes_by_cid.setdefault(cid, []).append(
cast(
"tuple[str, bytes, str, int]",
(r["type"], r["blob"], r["task_id"], r["idx"]),
)
)
else: # kind == "b"
seed_blob = cast("tuple[str, bytes]", (r["type"], r["blob"]))
for ws in writes_by_cid.values():
ws.sort(key=lambda w: (w[2], w[3]), reverse=True)
if not chain_cids:
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=[])
collected: list[PendingWrite] = []
for cid in chain_cids:
for type_tag, write_blob, task_id, _idx in writes_by_cid.get(cid, []):
val = self.serde.loads_typed((type_tag, write_blob))
collected.append((task_id, channel, val))
seed: Any = DELTA_SENTINEL
if seed_blob is not None and seed_blob[0] != "empty":
seed = self.serde.loads_typed(seed_blob)
collected.reverse()
return _ChannelWritesHistory(seed=seed, writes=collected)
def _dump_blobs(
self,
thread_id: str,
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-checkpoint-postgres"
version = "3.0.5"
version = "3.1.0a3"
description = "Library with a Postgres 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>=2.1.2,<5.0.0",
"langgraph-checkpoint>=4.1.0a3,<5.0.0",
"orjson>=3.11.5",
"psycopg>=3.2.0",
"psycopg-pool>=3.2.0",
@@ -20,7 +20,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres"
Twitter = "https://x.com/LangChain"
Twitter = "https://x.com/langchain_oss"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+46 -2
View File
@@ -361,9 +361,9 @@ async def test_get_checkpoint_no_channel_values(
load_checkpoint_tuple = saver._load_checkpoint_tuple
def patched_load_checkpoint_tuple(value):
async def patched_load_checkpoint_tuple(value):
value["checkpoint"].pop("channel_values", None)
return load_checkpoint_tuple(value)
return await load_checkpoint_tuple(value)
monkeypatch.setattr(
saver, "_load_checkpoint_tuple", patched_load_checkpoint_tuple
@@ -371,3 +371,47 @@ async def test_get_checkpoint_no_channel_values(
checkpoint = await saver.aget_tuple(config)
assert checkpoint.checkpoint["channel_values"] == {}
@pytest.mark.parametrize("saver_name", ["base", "pool", "pipe"])
async def test_delta_channel_chain_reconstruction(saver_name: str) -> None:
"""AsyncPostgresSaver reconstructs DeltaChannel chain via point-lookup traversal."""
pytest.importorskip(
"langgraph.channels.delta", reason="langgraph core not installed"
)
from typing import Annotated
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import START, StateGraph
from langgraph.graph.message import _messages_delta_reducer
from typing_extensions import TypedDict
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
n = len(state["messages"])
return {"messages": [AIMessage(content=f"reply-{n}", id=f"ai-{n}")]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
async with _saver(saver_name) as saver:
graph = builder.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "diff-channel-test-1"}}
await graph.ainvoke({"messages": [HumanMessage(content="hi", id="h1")]}, config)
await graph.ainvoke(
{"messages": [HumanMessage(content="there", id="h2")]}, config
)
state = await graph.aget_state(config)
msgs = state.values["messages"]
assert len(msgs) == 4, f"expected 4, got {len(msgs)}: {msgs}"
assert msgs[0].content == "hi"
assert msgs[1].content == "reply-1"
assert msgs[2].content == "there"
assert msgs[3].content == "reply-3"
+198 -75
View File
@@ -1,6 +1,10 @@
version = 1
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version < '3.15'",
]
[[package]]
name = "annotated-types"
@@ -240,7 +244,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.22"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -252,14 +256,14 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.1.0a3"
source = { editable = "../checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -307,7 +311,7 @@ test = [
[[package]]
name = "langgraph-checkpoint-postgres"
version = "3.0.5"
version = "3.1.0a3"
source = { editable = "." }
dependencies = [
{ name = "langgraph-checkpoint" },
@@ -382,7 +386,7 @@ test = [
[[package]]
name = "langsmith"
version = "0.6.4"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -392,11 +396,12 @@ dependencies = [
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "uuid-utils" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/85/9c7933052a997da1b85bc5c774f3865e9b1da1c8d71541ea133178b13229/langsmith-0.6.4.tar.gz", hash = "sha256:36f7223a01c218079fbb17da5e536ebbaf5c1468c028abe070aa3ae59bc99ec8", size = 919964, upload-time = "2026-01-15T20:02:28.873Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/0f/09a6637a7ba777eb307b7c80852d9ee26438e2bdafbad6fcc849ff9d9192/langsmith-0.6.4-py3-none-any.whl", hash = "sha256:ac4835860160be371042c7adbba3cb267bcf8d96a5ea976c33a8a4acad6c5486", size = 283503, upload-time = "2026-01-15T20:02:26.662Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
@@ -486,7 +491,7 @@ wheels = [
[[package]]
name = "mypy"
version = "1.20.0"
version = "1.20.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
@@ -495,51 +500,51 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" }
sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/a2/a965c8c3fcd4fa8b84ba0d46606181b0d0a1d50f274c67877f3e9ed4882c/mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8", size = 14430138, upload-time = "2026-03-31T16:52:37.843Z" },
{ url = "https://files.pythonhosted.org/packages/53/6e/043477501deeb8eabbab7f1a2f6cac62cfb631806dc1d6862a04a7f5011b/mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a", size = 13311282, upload-time = "2026-03-31T16:55:11.021Z" },
{ url = "https://files.pythonhosted.org/packages/65/aa/bd89b247b83128197a214f29f0632ff3c14f54d4cd70d144d157bd7d7d6e/mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865", size = 13750889, upload-time = "2026-03-31T16:52:02.909Z" },
{ url = "https://files.pythonhosted.org/packages/fa/9d/2860be7355c45247ccc0be1501c91176318964c2a137bd4743f58ce6200e/mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca", size = 14619788, upload-time = "2026-03-31T16:50:48.928Z" },
{ url = "https://files.pythonhosted.org/packages/75/7f/3ef3e360c91f3de120f205c8ce405e9caf9fc52ef14b65d37073e322c114/mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018", size = 14918849, upload-time = "2026-03-31T16:51:10.478Z" },
{ url = "https://files.pythonhosted.org/packages/ae/72/af970dfe167ef788df7c5e6109d2ed0229f164432ce828bc9741a4250e64/mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13", size = 10822007, upload-time = "2026-03-31T16:50:25.268Z" },
{ url = "https://files.pythonhosted.org/packages/93/94/ba9065c2ebe5421619aff684b793d953e438a8bfe31a320dd6d1e0706e81/mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281", size = 9756158, upload-time = "2026-03-31T16:48:36.213Z" },
{ url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" },
{ url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" },
{ url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" },
{ url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" },
{ url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" },
{ url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" },
{ url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" },
{ url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" },
{ url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" },
{ url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" },
{ url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" },
{ url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" },
{ url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" },
{ url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" },
{ url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" },
{ url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" },
{ url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" },
{ url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" },
{ url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" },
{ url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" },
{ url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" },
{ url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" },
{ url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" },
{ url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" },
{ url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" },
{ url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" },
{ url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" },
{ url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" },
{ url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" },
{ url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" },
{ url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" },
{ url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" },
{ url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" },
{ url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" },
{ url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" },
{ url = "https://files.pythonhosted.org/packages/76/97/ce2502df2cecf2ef997b6c6527c4a223b92feb9e7b790cdc8dcd683f3a8a/mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4", size = 14457059, upload-time = "2026-04-21T17:06:14.935Z" },
{ url = "https://files.pythonhosted.org/packages/c9/34/417ee60b822cc80c0f3dc9f495ad7fd8dbb8d8b2cf4baf22d4046d25d01d/mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997", size = 13346816, upload-time = "2026-04-21T17:10:41.433Z" },
{ url = "https://files.pythonhosted.org/packages/4a/85/e20951978702df58379d0bcc2e8f7ccdca4e78cd7dc66dd3ddbf9b29d517/mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14", size = 13772593, upload-time = "2026-04-21T17:08:11.24Z" },
{ url = "https://files.pythonhosted.org/packages/63/a5/5441a13259ec516c56fd5de0fd96a69a9590ae6c5e5d3e5174aa84b97973/mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99", size = 14656635, upload-time = "2026-04-21T17:09:54.042Z" },
{ url = "https://files.pythonhosted.org/packages/3b/51/b89c69157c5e1f19fd125a65d991166a26906e7902f026f00feebbcfa2b9/mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c", size = 14943278, upload-time = "2026-04-21T17:09:15.599Z" },
{ url = "https://files.pythonhosted.org/packages/e9/44/6b0eeecfe96d7cce1d71c66b8e03cb304aa70ec11f1955dc1d6b46aca3c3/mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd", size = 10851915, upload-time = "2026-04-21T17:06:03.5Z" },
{ url = "https://files.pythonhosted.org/packages/3c/36/6593dc88545d75fb96416184be5392da5e2a8e8c2802a8597913e16ae25c/mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2", size = 9786676, upload-time = "2026-04-21T17:07:02.035Z" },
{ url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" },
{ url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" },
{ url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" },
{ url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" },
{ url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" },
{ url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" },
{ url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" },
{ url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" },
{ url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" },
{ url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" },
{ url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" },
{ url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" },
{ url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" },
{ url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" },
{ url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" },
{ url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" },
{ url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" },
{ url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" },
{ url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" },
{ url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" },
{ url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" },
{ url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" },
{ url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" },
{ url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" },
{ url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" },
{ url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" },
{ url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" },
{ url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" },
{ url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" },
{ url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" },
{ url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" },
{ url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" },
{ url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
]
[[package]]
@@ -950,7 +955,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.0.2"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -961,9 +966,9 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
@@ -1098,27 +1103,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.8"
version = "0.15.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" }
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" },
{ url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" },
{ url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" },
{ url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" },
{ url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" },
{ url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" },
{ url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" },
{ url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" },
{ url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" },
{ url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" },
{ url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" },
{ url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" },
{ url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" },
{ url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" },
{ url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" },
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
]
[[package]]
@@ -1284,6 +1289,124 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]]
name = "xxhash"
version = "3.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" },
{ url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" },
{ url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" },
{ url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" },
{ url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" },
{ url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" },
{ url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" },
{ url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" },
{ url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" },
{ url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" },
{ url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" },
{ url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" },
{ url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" },
{ url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" },
{ url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" },
{ url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" },
{ url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" },
{ url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" },
{ url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" },
{ url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" },
{ url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" },
{ url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" },
{ url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" },
{ url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" },
{ url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" },
{ url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" },
{ url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" },
{ url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" },
{ url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" },
{ url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" },
{ url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" },
{ url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" },
{ url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" },
{ url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" },
{ url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" },
{ url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" },
{ url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" },
{ url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" },
{ url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" },
{ url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" },
{ url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" },
{ url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" },
{ url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" },
{ url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" },
{ url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" },
{ url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" },
{ url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" },
{ url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" },
{ url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" },
{ url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" },
{ url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" },
{ url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" },
{ url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" },
{ url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" },
{ url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" },
{ url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" },
{ url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" },
{ url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" },
{ url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" },
{ url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" },
{ url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" },
{ url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" },
{ url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" },
{ url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" },
{ url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" },
{ url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" },
{ url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" },
{ url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" },
{ url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" },
{ url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" },
{ url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" },
{ url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" },
{ url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" },
{ url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" },
{ url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" },
{ url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" },
{ url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" },
{ url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" },
{ url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" },
{ url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" },
{ url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" },
{ url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" },
{ url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" },
{ url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" },
{ url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" },
{ url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" },
{ url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" },
{ url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" },
{ url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" },
{ url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" },
{ url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" },
{ url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" },
{ url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" },
{ url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" },
{ url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" },
]
[[package]]
name = "zstandard"
version = "0.25.0"
+5
View File
@@ -2,6 +2,11 @@
Implementation of LangGraph CheckpointSaver that uses SQLite DB (both sync and async, via `aiosqlite`)
## Security
> [!IMPORTANT]
> Set `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list when creating your checkpointer. This restricts checkpoint deserialization to known-safe types, preventing code execution if the database is compromised. See the [langgraph-checkpoint README](https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint#serde) for details.
## Usage
```python
+1 -1
View File
@@ -19,7 +19,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite"
Twitter = "https://x.com/LangChain"
Twitter = "https://x.com/langchain_oss"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+197 -74
View File
@@ -1,6 +1,10 @@
version = 1
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version < '3.15'",
]
[[package]]
name = "aiosqlite"
@@ -249,7 +253,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.22"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -261,14 +265,14 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.1.0a3"
source = { editable = "../checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -385,7 +389,7 @@ test = [
[[package]]
name = "langsmith"
version = "0.6.4"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -395,11 +399,12 @@ dependencies = [
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "uuid-utils" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/85/9c7933052a997da1b85bc5c774f3865e9b1da1c8d71541ea133178b13229/langsmith-0.6.4.tar.gz", hash = "sha256:36f7223a01c218079fbb17da5e536ebbaf5c1468c028abe070aa3ae59bc99ec8", size = 919964, upload-time = "2026-01-15T20:02:28.873Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/0f/09a6637a7ba777eb307b7c80852d9ee26438e2bdafbad6fcc849ff9d9192/langsmith-0.6.4-py3-none-any.whl", hash = "sha256:ac4835860160be371042c7adbba3cb267bcf8d96a5ea976c33a8a4acad6c5486", size = 283503, upload-time = "2026-01-15T20:02:26.662Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
@@ -489,7 +494,7 @@ wheels = [
[[package]]
name = "mypy"
version = "1.20.0"
version = "1.20.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
@@ -498,51 +503,51 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" }
sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/a2/a965c8c3fcd4fa8b84ba0d46606181b0d0a1d50f274c67877f3e9ed4882c/mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8", size = 14430138, upload-time = "2026-03-31T16:52:37.843Z" },
{ url = "https://files.pythonhosted.org/packages/53/6e/043477501deeb8eabbab7f1a2f6cac62cfb631806dc1d6862a04a7f5011b/mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a", size = 13311282, upload-time = "2026-03-31T16:55:11.021Z" },
{ url = "https://files.pythonhosted.org/packages/65/aa/bd89b247b83128197a214f29f0632ff3c14f54d4cd70d144d157bd7d7d6e/mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865", size = 13750889, upload-time = "2026-03-31T16:52:02.909Z" },
{ url = "https://files.pythonhosted.org/packages/fa/9d/2860be7355c45247ccc0be1501c91176318964c2a137bd4743f58ce6200e/mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca", size = 14619788, upload-time = "2026-03-31T16:50:48.928Z" },
{ url = "https://files.pythonhosted.org/packages/75/7f/3ef3e360c91f3de120f205c8ce405e9caf9fc52ef14b65d37073e322c114/mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018", size = 14918849, upload-time = "2026-03-31T16:51:10.478Z" },
{ url = "https://files.pythonhosted.org/packages/ae/72/af970dfe167ef788df7c5e6109d2ed0229f164432ce828bc9741a4250e64/mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13", size = 10822007, upload-time = "2026-03-31T16:50:25.268Z" },
{ url = "https://files.pythonhosted.org/packages/93/94/ba9065c2ebe5421619aff684b793d953e438a8bfe31a320dd6d1e0706e81/mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281", size = 9756158, upload-time = "2026-03-31T16:48:36.213Z" },
{ url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" },
{ url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" },
{ url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" },
{ url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" },
{ url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" },
{ url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" },
{ url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" },
{ url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" },
{ url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" },
{ url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" },
{ url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" },
{ url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" },
{ url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" },
{ url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" },
{ url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" },
{ url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" },
{ url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" },
{ url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" },
{ url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" },
{ url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" },
{ url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" },
{ url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" },
{ url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" },
{ url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" },
{ url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" },
{ url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" },
{ url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" },
{ url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" },
{ url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" },
{ url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" },
{ url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" },
{ url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" },
{ url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" },
{ url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" },
{ url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" },
{ url = "https://files.pythonhosted.org/packages/76/97/ce2502df2cecf2ef997b6c6527c4a223b92feb9e7b790cdc8dcd683f3a8a/mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4", size = 14457059, upload-time = "2026-04-21T17:06:14.935Z" },
{ url = "https://files.pythonhosted.org/packages/c9/34/417ee60b822cc80c0f3dc9f495ad7fd8dbb8d8b2cf4baf22d4046d25d01d/mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997", size = 13346816, upload-time = "2026-04-21T17:10:41.433Z" },
{ url = "https://files.pythonhosted.org/packages/4a/85/e20951978702df58379d0bcc2e8f7ccdca4e78cd7dc66dd3ddbf9b29d517/mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14", size = 13772593, upload-time = "2026-04-21T17:08:11.24Z" },
{ url = "https://files.pythonhosted.org/packages/63/a5/5441a13259ec516c56fd5de0fd96a69a9590ae6c5e5d3e5174aa84b97973/mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99", size = 14656635, upload-time = "2026-04-21T17:09:54.042Z" },
{ url = "https://files.pythonhosted.org/packages/3b/51/b89c69157c5e1f19fd125a65d991166a26906e7902f026f00feebbcfa2b9/mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c", size = 14943278, upload-time = "2026-04-21T17:09:15.599Z" },
{ url = "https://files.pythonhosted.org/packages/e9/44/6b0eeecfe96d7cce1d71c66b8e03cb304aa70ec11f1955dc1d6b46aca3c3/mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd", size = 10851915, upload-time = "2026-04-21T17:06:03.5Z" },
{ url = "https://files.pythonhosted.org/packages/3c/36/6593dc88545d75fb96416184be5392da5e2a8e8c2802a8597913e16ae25c/mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2", size = 9786676, upload-time = "2026-04-21T17:07:02.035Z" },
{ url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" },
{ url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" },
{ url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" },
{ url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" },
{ url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" },
{ url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" },
{ url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" },
{ url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" },
{ url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" },
{ url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" },
{ url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" },
{ url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" },
{ url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" },
{ url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" },
{ url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" },
{ url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" },
{ url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" },
{ url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" },
{ url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" },
{ url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" },
{ url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" },
{ url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" },
{ url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" },
{ url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" },
{ url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" },
{ url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" },
{ url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" },
{ url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" },
{ url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" },
{ url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" },
{ url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" },
{ url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" },
{ url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" },
{ url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
]
[[package]]
@@ -862,7 +867,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.0.2"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -873,9 +878,9 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
@@ -1022,27 +1027,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.8"
version = "0.15.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" }
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" },
{ url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" },
{ url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" },
{ url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" },
{ url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" },
{ url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" },
{ url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" },
{ url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" },
{ url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" },
{ url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" },
{ url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" },
{ url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" },
{ url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" },
{ url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" },
{ url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" },
{ url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" },
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
]
[[package]]
@@ -1211,6 +1216,124 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]]
name = "xxhash"
version = "3.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" },
{ url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" },
{ url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" },
{ url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" },
{ url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" },
{ url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" },
{ url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" },
{ url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" },
{ url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" },
{ url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" },
{ url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" },
{ url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" },
{ url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" },
{ url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" },
{ url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" },
{ url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" },
{ url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" },
{ url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" },
{ url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" },
{ url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" },
{ url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" },
{ url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" },
{ url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" },
{ url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" },
{ url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" },
{ url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" },
{ url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" },
{ url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" },
{ url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" },
{ url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" },
{ url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" },
{ url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" },
{ url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" },
{ url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" },
{ url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" },
{ url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" },
{ url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" },
{ url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" },
{ url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" },
{ url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" },
{ url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" },
{ url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" },
{ url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" },
{ url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" },
{ url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" },
{ url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" },
{ url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" },
{ url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" },
{ url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" },
{ url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" },
{ url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" },
{ url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" },
{ url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" },
{ url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" },
{ url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" },
{ url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" },
{ url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" },
{ url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" },
{ url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" },
{ url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" },
{ url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" },
{ url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" },
{ url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" },
{ url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" },
{ url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" },
{ url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" },
{ url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" },
{ url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" },
{ url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" },
{ url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" },
{ url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" },
{ url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" },
{ url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" },
{ url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" },
{ url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" },
{ url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" },
{ url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" },
{ url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" },
{ url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" },
{ url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" },
{ url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" },
{ url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" },
{ url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" },
{ url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" },
{ url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" },
{ url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" },
{ url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" },
{ url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" },
{ url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" },
{ url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" },
{ url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" },
{ url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" },
{ url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" },
{ url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" },
{ url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" },
]
[[package]]
name = "zstandard"
version = "0.25.0"
+3
View File
@@ -26,6 +26,9 @@ You must pass these when invoking the graph as part of the configurable part of
`langgraph_checkpoint` also defines protocol for serialization/deserialization (serde) and provides an default implementation (`langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer`) that handles a wide variety of types, including LangChain and LangGraph primitives, datetimes, enums and more.
> [!IMPORTANT]
> **Checkpoint deserialization security:** By default the serializer allows any Python type found in checkpoint data. New applications should set the environment variable `LANGGRAPH_STRICT_MSGPACK=true` or pass an explicit `allowed_msgpack_modules` list to `JsonPlusSerializer` to restrict deserialization to known-safe types.
### Pending writes
When a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes.
@@ -3,7 +3,7 @@ from __future__ import annotations
import copy
import logging
from collections.abc import AsyncIterator, Collection, Iterator, Mapping, Sequence
from typing import ( # noqa: UP035
from typing import (
Any,
Generic,
Literal,
@@ -18,6 +18,9 @@ from langgraph.checkpoint.base.id import uuid6
from langgraph.checkpoint.serde.base import SerializerProtocol, maybe_add_typed_methods
from langgraph.checkpoint.serde.encrypted import EncryptedSerializer
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
from langgraph.checkpoint.serde.types import (
DELTA_SENTINEL as DELTA_SENTINEL,
)
from langgraph.checkpoint.serde.types import (
ERROR,
INTERRUPT,
@@ -28,6 +31,8 @@ from langgraph.checkpoint.serde.types import (
V = TypeVar("V", int, float, str)
PendingWrite = tuple[str, str, Any]
logger = logging.getLogger(__name__)
@@ -119,6 +124,30 @@ class CheckpointTuple(NamedTuple):
pending_writes: list[PendingWrite] | None = None
class _ChannelWritesHistory(NamedTuple):
"""Result of `BaseCheckpointSaver._get_channel_writes_history`.
Storage-level view of what one channel wrote across the ancestor chain
of a target checkpoint:
* `seed` — the nearest ancestor's stored blob value for this channel,
or `DELTA_SENTINEL` if the walk reached the root without finding a
stored value. A non-sentinel seed typically indicates a pre-delta
snapshot preserved across a channel-type migration (e.g.
`BinaryOperatorAggregate` storage extended under `DeltaChannel`).
* `writes` — on-path deltas oldest→newest, one `PendingWrite` per
step that wrote to this channel. Writes stored at the target
checkpoint itself are pending for the next super-step and are
excluded.
Experimental: method surface may change; the NamedTuple shape is the
contract.
"""
seed: Any
writes: list[PendingWrite]
class BaseCheckpointSaver(Generic[V]):
"""Base class for creating a graph checkpointer.
@@ -457,6 +486,104 @@ class BaseCheckpointSaver(Generic[V]):
"""
raise NotImplementedError
def _get_tuple_raw(self, config: RunnableConfig) -> CheckpointTuple | None:
"""Pure storage read used by `_get_channel_writes_history`.
Must return the same value as `get_tuple` but must NOT trigger channel
reconstruction; otherwise the channel-hydration path would re-enter
`_get_channel_writes_history`. Override only if `get_tuple` itself
performs channel hydration.
"""
return self.get_tuple(config)
async def _aget_tuple_raw(self, config: RunnableConfig) -> CheckpointTuple | None:
"""Async version of `_get_tuple_raw`. See docstring there."""
return await self.aget_tuple(config)
def _get_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
"""**Experimental.** Query one channel's writes along the parent chain.
Storage-level query, not channel semantics: returns `(seed, writes)`
reflecting what storage knows about a single channel across the
ancestor chain of the target checkpoint identified by `config`.
* `writes` — on-path deltas oldest→newest as `PendingWrite` tuples.
Writes stored at the target `checkpoint_id` itself are pending
for the next super-step and are excluded.
* `seed` — the nearest ancestor's stored blob value for this
channel; `DELTA_SENTINEL` if the walk reached the root without
finding a stored value. A non-sentinel seed typically indicates
a pre-delta snapshot preserved across a channel-type migration.
Walks the **parent chain** (not `list(before=...)`): for forked
threads, only on-path ancestors contribute.
Reference implementation walks `get_tuple` + `parent_config`,
inspecting each ancestor's `channel_values[channel]` for the seed
terminator. Savers with direct storage access (`InMemorySaver`,
`PostgresSaver`) override for performance; the return contract is
fixed here.
Underscore-prefixed because the method surface is experimental.
"""
collected: list[PendingWrite] = [] # newest first; reversed at the end
target_tuple = self._get_tuple_raw(config)
cursor_config: RunnableConfig | None = (
target_tuple.parent_config if target_tuple else None
)
while cursor_config is not None:
tup = self._get_tuple_raw(cursor_config)
if tup is None:
break
# Collect this ancestor's writes FIRST — they encode the
# transition from this ancestor's state to its child's, so
# they must be included whether or not this ancestor is the
# seed terminator.
if tup.pending_writes:
# Within a superstep, pending_writes are oldest→newest;
# reverse to scan newest-first.
for write in reversed(tup.pending_writes):
if write[1] != channel:
continue
collected.append(write)
# Seed terminator: any non-sentinel blob on an ancestor
# establishes the reconstruction base. Stop here.
ancestor_value = tup.checkpoint["channel_values"].get(channel)
if ancestor_value is not None and ancestor_value is not DELTA_SENTINEL:
collected.reverse()
return _ChannelWritesHistory(seed=ancestor_value, writes=collected)
cursor_config = tup.parent_config
collected.reverse()
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=collected)
async def _aget_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
"""Async version of `_get_channel_writes_history`. See docstring there."""
collected: list[PendingWrite] = []
target_tuple = await self._aget_tuple_raw(config)
cursor_config: RunnableConfig | None = (
target_tuple.parent_config if target_tuple else None
)
while cursor_config is not None:
tup = await self._aget_tuple_raw(cursor_config)
if tup is None:
break
if tup.pending_writes:
for write in reversed(tup.pending_writes):
if write[1] != channel:
continue
collected.append(write)
ancestor_value = tup.checkpoint["channel_values"].get(channel)
if ancestor_value is not None and ancestor_value is not DELTA_SENTINEL:
collected.reverse()
return _ChannelWritesHistory(seed=ancestor_value, writes=collected)
cursor_config = tup.parent_config
collected.reverse()
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=collected)
def get_next_version(self, current: V | None, channel: None) -> V:
"""Generate the next version ID for a channel.
@@ -14,16 +14,20 @@ from typing import Any
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
WRITES_IDX_MAP,
BaseCheckpointSaver,
ChannelVersions,
Checkpoint,
CheckpointMetadata,
CheckpointTuple,
PendingWrite,
SerializerProtocol,
_ChannelWritesHistory,
get_checkpoint_id,
get_checkpoint_metadata,
)
from langgraph.checkpoint.serde.types import _DeltaSnapshot
logger = logging.getLogger(__name__)
@@ -121,16 +125,114 @@ class InMemorySaver(
return self.stack.__exit__(__exc_type, __exc_value, __traceback)
def _load_blobs(
self, thread_id: str, checkpoint_ns: str, versions: ChannelVersions
self,
thread_id: str,
checkpoint_ns: str,
versions: ChannelVersions,
) -> dict[str, Any]:
channel_values: dict[str, Any] = {}
for k, v in versions.items():
kk = (thread_id, checkpoint_ns, k, v)
if kk in self.blobs:
vv = self.blobs[kk]
if vv[0] != "empty":
channel_values[k] = self.serde.loads_typed(vv)
return channel_values
result: dict[str, Any] = {}
for k, ver in versions.items():
kk = (thread_id, checkpoint_ns, k, ver)
if kk not in self.blobs:
continue
vv = self.blobs[kk]
if vv[0] == "empty":
continue
result[k] = self.serde.loads_typed(vv)
return result
def _get_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
thread_id = config["configurable"]["thread_id"]
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
checkpoint_id = config["configurable"].get("checkpoint_id", "")
ns_storage = self.storage.get(thread_id, {}).get(checkpoint_ns, {})
# Walk the parent chain newest→oldest. Skip the target itself —
# writes stored AT `checkpoint_id` are pending for the next step
# (pregel applies them via `apply_writes`; they aren't part of the
# snapshot value AT `checkpoint_id`).
chain: list[str] = []
target_entry = ns_storage.get(checkpoint_id)
current: str | None = target_entry[2] if target_entry is not None else None
while current is not None:
entry = ns_storage.get(current)
if entry is None:
break
chain.append(current)
_, _, parent = entry
current = parent
# Scan newest→oldest. A pre-delta blob on an ancestor terminates the
# walk and is bound as `seed`; without this, a thread migrated from
# pre-delta storage would replay ancestor writes all the way to the
# root AND miss any value that lived only in the old blob (e.g. from
# `update_state`).
#
# At each ancestor, check the blob BEFORE processing its pending
# writes: a pre-delta blob represents the state AT that ancestor,
# which already subsumes any writes stored under it. Processing
# those writes first would fold them into the reconstructed value
# twice (once via the blob, once via replay).
collected: list[PendingWrite] = [] # newest first
for cp_id in chain: # newest → oldest
entry = ns_storage.get(cp_id)
if entry is not None:
ckpt = self.serde.loads_typed(entry[0])
ver = ckpt.get("channel_versions", {}).get(channel)
if ver is not None:
blob_entry = self.blobs.get(
(thread_id, checkpoint_ns, channel, ver)
)
if blob_entry is not None and blob_entry[0] != "empty":
blob_value = self.serde.loads_typed(blob_entry)
if blob_value is not DELTA_SENTINEL:
if isinstance(blob_value, _DeltaSnapshot):
# Step-based snapshot: the blob is state AT this
# ancestor, but the ancestor's pending_writes
# encode the NEXT step's transition and are NOT
# subsumed by the snapshot — collect them first.
step_writes = self.writes.get(
(thread_id, checkpoint_ns, cp_id), {}
)
for (_task_id, _idx), (
tid,
ch,
serialized,
_,
) in sorted(step_writes.items(), reverse=True):
if ch != channel:
continue
collected.append(
(tid, ch, self.serde.loads_typed(serialized))
)
collected.reverse()
return _ChannelWritesHistory(
seed=blob_value, writes=collected
)
# Pre-delta blob: state AT this ancestor already
# subsumes its pending_writes — skip them.
collected.reverse()
return _ChannelWritesHistory(
seed=blob_value, writes=collected
)
step_writes = self.writes.get((thread_id, checkpoint_ns, cp_id), {})
# Within a superstep, sorted by (task_id, idx) = oldest → newest;
# reverse for newest-first scan.
for (_task_id, _idx), (tid, ch, serialized, _) in sorted(
step_writes.items(), reverse=True
):
if ch != channel:
continue
val = self.serde.loads_typed(serialized)
collected.append((tid, ch, val))
collected.reverse()
return _ChannelWritesHistory(seed=DELTA_SENTINEL, writes=collected)
async def _aget_channel_writes_history(
self, config: RunnableConfig, channel: str
) -> _ChannelWritesHistory:
return self._get_channel_writes_history(config, channel)
def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
"""Get a checkpoint tuple from the in-memory storage.
@@ -350,7 +452,9 @@ class InMemorySaver(
values: dict[str, Any] = c.pop("channel_values") # type: ignore[misc]
for k, v in new_versions.items():
self.blobs[(thread_id, checkpoint_ns, k, v)] = (
self.serde.dumps_typed(values[k]) if k in values else ("empty", b"")
self.serde.dumps_typed(values[k])
if k in values and values[k] is not DELTA_SENTINEL
else ("empty", b"")
)
self.storage[thread_id][checkpoint_ns].update(
{
@@ -1,3 +1,10 @@
"""Msgpack deserialization safety controls.
Set ``LANGGRAPH_STRICT_MSGPACK=true`` to restrict checkpoint deserialization
to the types listed in ``SAFE_MSGPACK_TYPES``. Without this, any Python
callable stored in checkpoint data will be imported and executed on load.
"""
import os
from collections.abc import Iterable
from typing import cast
@@ -66,6 +73,7 @@ SAFE_MSGPACK_TYPES: frozenset[tuple[str, ...]] = frozenset(
("langchain_core.documents.base", "Document"),
# langgraph
("langgraph.types", "Send"),
("langgraph.types", "TimeoutPolicy"),
("langgraph.types", "Interrupt"),
("langgraph.types", "Command"),
("langgraph.types", "StateSnapshot"),
@@ -33,19 +33,51 @@ from langchain_core.load.load import Reviver
from langgraph.checkpoint.serde import _msgpack as _lg_msgpack
from langgraph.checkpoint.serde.base import SerializerProtocol
from langgraph.checkpoint.serde.event_hooks import emit_serde_event
from langgraph.checkpoint.serde.types import SendProtocol
from langgraph.checkpoint.serde.types import (
SendProtocol,
_DeltaSnapshot,
)
from langgraph.store.base import Item
if TYPE_CHECKING:
from langgraph.checkpoint.serde._msgpack import (
AllowedMsgpackModules,
)
from langgraph.checkpoint.serde.types import SendProtocol
LC_REVIVER = Reviver()
EMPTY_BYTES = b""
logger = logging.getLogger(__name__)
# Dedup log warnings across process lifetime; cap bounds state if types are
# dynamically generated (also acts as a circuit breaker on warning volume).
# Dedup is best-effort: racing threads may each emit once for the same key,
# and warnings are silently dropped once _MAX_WARNED_TYPES is reached.
_MAX_WARNED_TYPES = 1000
_warned_unregistered_types: set[tuple[str, str]] = set()
_warned_blocked_types: set[tuple[str, str]] = set()
def _is_safe_json_type(id_list: list[str]) -> bool:
"""Return True if an lc=2 id refers to a type in SAFE_MSGPACK_TYPES.
Safe types bypass the ``allowed_json_modules`` gate so that old "json" format
checkpoints (written before the msgpack migration) can be resumed without
requiring users to configure an explicit allowlist.
"""
if len(id_list) < 2:
return False
module_name = ".".join(id_list[:-1])
return (module_name, id_list[-1]) in _lg_msgpack.SAFE_MSGPACK_TYPES
def _warn_once(
seen: set[tuple[str, str]], key: tuple[str, str], msg: str, *args: object
) -> None:
if key in seen or len(seen) >= _MAX_WARNED_TYPES:
return
seen.add(key)
logger.warning(msg, *args)
class JsonPlusSerializer(SerializerProtocol):
"""Serializer that uses ormsgpack, with optional fallbacks.
@@ -56,6 +88,10 @@ class JsonPlusSerializer(SerializerProtocol):
class and called within the Pregel loop. It should not be used on untrusted
python objects. If an attacker can write directly to your checkpoint database,
they may be able to trigger code execution when data is deserialized.
Set the environment variable ``LANGGRAPH_STRICT_MSGPACK=true`` to restrict
deserialization to a built-in allowlist of safe types. You can also pass
an explicit ``allowed_msgpack_modules`` to the constructor.
"""
def __init__(
@@ -70,8 +106,11 @@ class JsonPlusSerializer(SerializerProtocol):
) -> None:
if allowed_msgpack_modules is _lg_msgpack._SENTINEL:
if _lg_msgpack.STRICT_MSGPACK_ENABLED:
# Strict: only SAFE_MSGPACK_TYPES are allowed.
allowed_msgpack_modules = None
else:
# Permissive (default): all types allowed with a warning.
# Set LANGGRAPH_STRICT_MSGPACK=true to lock this down.
allowed_msgpack_modules = True
self.pickle_fallback = pickle_fallback
self._allowed_json_modules: set[tuple[str, ...]] | Literal[True] | None = (
@@ -140,19 +179,23 @@ class JsonPlusSerializer(SerializerProtocol):
return out
def _reviver(self, value: dict[str, Any]) -> Any:
if self._allowed_json_modules and (
if (
value.get("lc", None) == 2
and value.get("type", None) == "constructor"
and value.get("id", None) is not None
):
try:
return self._revive_lc2(value)
except InvalidModuleError as e:
logger.warning(
"Object %s is not in the deserialization allowlist.\n%s",
value["id"],
e.message,
)
id_list = value["id"]
is_safe = _is_safe_json_type(id_list)
if self._allowed_json_modules or is_safe:
try:
return self._revive_lc2(value)
except InvalidModuleError as e:
if not is_safe:
logger.warning(
"Object %s is not in the deserialization allowlist.\n%s",
value["id"],
e.message,
)
return LC_REVIVER(value)
@@ -200,6 +243,13 @@ class JsonPlusSerializer(SerializerProtocol):
method_display = "<init>"
dotted = ".".join(needed)
# Safe types (the same set already allowed for msgpack deserialization) are
# permitted without an explicit allowlist — they are known-safe LangGraph and
# LangChain types. This restores backwards-compat for old "json" checkpoints
# that pre-date the msgpack migration without reopening the broader security gate.
if _is_safe_json_type(list(needed)):
return
if not self._allowed_json_modules:
raise InvalidModuleError(
f"Refused to deserialize JSON constructor: {dotted} (method: {method_display}). "
@@ -269,10 +319,13 @@ EXT_METHOD_SINGLE_ARG = 3
EXT_PYDANTIC_V1 = 4
EXT_PYDANTIC_V2 = 5
EXT_NUMPY_ARRAY = 6
EXT_DELTA_SNAPSHOT = 7
def _msgpack_default(obj: Any) -> str | ormsgpack.Ext:
if hasattr(obj, "model_dump") and callable(obj.model_dump): # pydantic v2
if isinstance(obj, _DeltaSnapshot):
return ormsgpack.Ext(EXT_DELTA_SNAPSHOT, _msgpack_enc(obj.value))
elif hasattr(obj, "model_dump") and callable(obj.model_dump): # pydantic v2
return ormsgpack.Ext(
EXT_PYDANTIC_V2,
_msgpack_enc(
@@ -444,10 +497,13 @@ def _msgpack_default(obj: Any) -> str | ormsgpack.Ext:
),
)
elif isinstance(obj, SendProtocol):
args: tuple[Any, ...] = (obj.node, obj.arg)
if (timeout := getattr(obj, "timeout", None)) is not None:
args = (obj.node, obj.arg, timeout)
return ormsgpack.Ext(
EXT_CONSTRUCTOR_POS_ARGS,
_msgpack_enc(
(obj.__class__.__module__, obj.__class__.__name__, (obj.node, obj.arg)),
(obj.__class__.__module__, obj.__class__.__name__, args),
),
)
elif dataclasses.is_dataclass(obj):
@@ -498,6 +554,15 @@ def _msgpack_default(obj: Any) -> str | ormsgpack.Ext:
raise TypeError(f"Object of type {obj.__class__.__name__} is not serializable")
def _send_from_args(args: Sequence[Any]) -> Any:
# ya we have a cyclic import here ¯\_(ツ)_/¯
from langgraph.types import Send # type: ignore
if len(args) == 2:
return Send(*args)
return Send(args[0], args[1], timeout=args[2])
def _create_msgpack_ext_hook(
allowed_modules: set[tuple[str, ...]] | Literal[True] | None,
) -> Callable[[int, bytes], Any]:
@@ -527,10 +592,13 @@ def _create_msgpack_ext_hook(
"name": name,
}
)
logger.warning(
_warn_once(
_warned_unregistered_types,
key,
"Deserializing unregistered type %s.%s from checkpoint. "
"This will be blocked in a future version. "
"Add to allowed_msgpack_modules to silence: [(%r, %r)]",
"Set LANGGRAPH_STRICT_MSGPACK=true to block now, or add "
"to allowed_msgpack_modules to allow explicitly: [(%r, %r)]",
module,
name,
module,
@@ -548,7 +616,9 @@ def _create_msgpack_ext_hook(
"name": name,
}
)
logger.warning(
_warn_once(
_warned_blocked_types,
key,
"Blocked deserialization of %s.%s - not in allowed_msgpack_modules. "
"Add to allowed_msgpack_modules to allow: [(%r, %r)]",
module,
@@ -581,7 +651,13 @@ def _create_msgpack_ext_hook(
return False
def ext_hook(code: int, data: bytes) -> Any:
if code == EXT_CONSTRUCTOR_SINGLE_ARG:
if code == EXT_DELTA_SNAPSHOT:
return _DeltaSnapshot(
ormsgpack.unpackb(
data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
)
)
elif code == EXT_CONSTRUCTOR_SINGLE_ARG:
try:
tup = ormsgpack.unpackb(
data, ext_hook=ext_hook, option=ormsgpack.OPT_NON_STR_KEYS
@@ -602,6 +678,8 @@ def _create_msgpack_ext_hook(
)
if not _check_allowed(tup[0], tup[1]):
return tup[2]
if tup[0] == "langgraph.types" and tup[1] == "Send":
return _send_from_args(tup[2])
# module, name, args
return getattr(importlib.import_module(tup[0]), tup[1])(*tup[2])
except Exception:
@@ -715,9 +793,7 @@ def _msgpack_ext_hook_to_json(code: int, data: bytes) -> Any:
option=ormsgpack.OPT_NON_STR_KEYS,
)
if tup[0] == "langgraph.types" and tup[1] == "Send":
from langgraph.types import Send # type: ignore
return Send(*tup[2])
return _send_from_args(tup[2])
# module, name, args
return tup[2]
except Exception:
@@ -1,6 +1,7 @@
from collections.abc import Sequence
from typing import (
Any,
NamedTuple,
Protocol,
TypeVar,
runtime_checkable,
@@ -14,6 +15,37 @@ INTERRUPT = "__interrupt__"
RESUME = "__resume__"
TASKS = "__pregel_tasks"
class _DeltaSentinel:
"""In-memory marker for a DeltaChannel field with no snapshot.
Never serialized to storage — checkpointers strip it before writing.
Compare with `is DELTA_SENTINEL`; always the same module-level instance.
"""
__slots__ = ()
def __repr__(self) -> str:
return "DELTA_SENTINEL"
DELTA_SENTINEL = _DeltaSentinel()
class _DeltaSnapshot(NamedTuple):
"""Snapshot blob for a DeltaChannel with finite snapshot_frequency.
Stored in checkpoint_blobs via the `EXT_DELTA_SNAPSHOT` msgpack ext code.
The ancestor walk in `_get_channel_writes_history` terminates when it
encounters this type (any non-sentinel blob stops the walk).
`from_checkpoint` reconstructs the channel value directly from `.value`
without replaying writes — the snapshot IS the accumulated state.
"""
value: Any
Value = TypeVar("Value", covariant=True)
Update = TypeVar("Update", contravariant=True)
C = TypeVar("C")
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.1.0a3"
description = "Library with base interfaces for LangGraph checkpoint savers."
authors = []
requires-python = ">=3.10"
@@ -18,7 +18,7 @@ dependencies = [
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint"
Twitter = "https://x.com/LangChain"
Twitter = "https://x.com/langchain_oss"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+9
View File
@@ -29,6 +29,8 @@ from langgraph.checkpoint.serde.jsonplus import (
EXT_METHOD_SINGLE_ARG,
JsonPlusSerializer,
_msgpack_enc,
_warned_blocked_types,
_warned_unregistered_types,
)
@@ -102,6 +104,13 @@ def test_msgpack_method_pathlib_blocked_encrypted_strict(
class TestEncryptedSerializerMsgpackAllowlist:
"""Test msgpack allowlist behavior through EncryptedSerializer."""
@pytest.fixture(autouse=True)
def _reset_warned_types(self) -> None:
# Warning dedup state is process-global; reset per-test so each case
# sees a fresh slate and assertions about warning emission are stable.
_warned_unregistered_types.clear()
_warned_blocked_types.clear()
def test_safe_types_no_warning(self, caplog: pytest.LogCaptureFixture) -> None:
"""Test safe types deserialize without warnings through encryption."""
serde = _make_encrypted_serde()
+67 -2
View File
@@ -35,6 +35,8 @@ from langgraph.checkpoint.serde.jsonplus import (
JsonPlusSerializer,
_msgpack_enc,
_msgpack_ext_hook_to_json,
_warned_blocked_types,
_warned_unregistered_types,
)
from langgraph.store.base import Item
@@ -331,6 +333,57 @@ def test_serde_jsonplus_bytes() -> None:
assert serde.loads_typed(dumped) == some_bytes
def test_lc2_json_safe_type_revives_without_allowlist() -> None:
"""Old 'json' blobs with lc=2 for safe types must revive without an explicit allowlist.
Regression test for: https://github.com/langchain-ai/langgraph/issues/7498
Threads checkpointed before v1.0.1 (pre-msgpack) stored messages as lc=2 JSON
constructor dicts. Resuming those threads must reconstruct proper BaseMessage objects
rather than returning raw dicts that cause MESSAGE_COERCION_FAILURE in add_messages.
"""
from langchain_core.messages import AIMessage
serde = JsonPlusSerializer() # default: _allowed_json_modules=None
human_blob = {
"lc": 2,
"type": "constructor",
"id": ["langchain_core", "messages", "human", "HumanMessage"],
"kwargs": {"content": "hello", "type": "human"},
}
ai_blob = {
"lc": 2,
"type": "constructor",
"id": ["langchain_core", "messages", "ai", "AIMessage"],
"kwargs": {"content": "hi there", "type": "ai"},
}
result = serde.loads_typed(("json", json.dumps([human_blob, ai_blob]).encode()))
assert len(result) == 2
assert isinstance(result[0], HumanMessage), (
f"Expected HumanMessage, got {type(result[0])}: {result[0]!r}\n"
"lc=2 JSON blobs for safe types must deserialize without an explicit allowlist"
)
assert result[0].content == "hello"
assert isinstance(result[1], AIMessage)
assert result[1].content == "hi there"
def test_lc2_json_unknown_type_stays_blocked_without_allowlist() -> None:
"""lc=2 JSON blobs for types NOT in SAFE_MSGPACK_TYPES still require an allowlist."""
serde = JsonPlusSerializer()
load = {
"lc": 2,
"type": "constructor",
"id": ["pprint", "pprint"],
"kwargs": {"object": "HELLO"},
}
# No allowlist configured → raw dict returned (not raised, not reconstructed)
result = serde.loads_typed(("json", json.dumps(load).encode()))
assert isinstance(result, dict), "Unknown lc=2 type must stay as raw dict"
assert result.get("lc") == 2
def test_deserde_invalid_module() -> None:
serde = JsonPlusSerializer()
load = {
@@ -580,6 +633,14 @@ def test_msgpack_safe_types_no_warning(caplog: pytest.LogCaptureFixture) -> None
assert result is not None
@pytest.fixture(autouse=True)
def _reset_warned_types() -> None:
# Warning dedup state is process-global; reset per-test so each case sees
# a fresh slate and assertions about warning emission are stable.
_warned_unregistered_types.clear()
_warned_blocked_types.clear()
def test_msgpack_pydantic_warns_by_default(caplog: pytest.LogCaptureFixture) -> None:
"""Pydantic models not in allowlist should log warning but still deserialize."""
current = _lg_msgpack.STRICT_MSGPACK_ENABLED
@@ -595,6 +656,12 @@ def test_msgpack_pydantic_warns_by_default(caplog: pytest.LogCaptureFixture) ->
assert "unregistered type" in caplog.text.lower()
assert "allowed_msgpack_modules" in caplog.text
assert result == obj
# Second deserialization of the same type should NOT produce another warning
caplog.clear()
result2 = serde.loads_typed(dumped)
assert "unregistered type" not in caplog.text.lower()
assert result2 == obj
_lg_msgpack.STRICT_MSGPACK_ENABLED = current
@@ -639,7 +706,6 @@ def test_msgpack_allowlist_silences_warning(caplog: pytest.LogCaptureFixture) ->
def test_msgpack_none_blocks_unregistered(caplog: pytest.LogCaptureFixture) -> None:
"""allowed_msgpack_modules=None should block unregistered types."""
serde = JsonPlusSerializer(allowed_msgpack_modules=None)
obj = MyPydantic(foo="test", bar=42, inner=InnerPydantic(hello="world"))
@@ -657,7 +723,6 @@ def test_msgpack_allowlist_blocks_non_listed(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Allowlists should block unregistered types even if msgpack is enabled."""
serde = JsonPlusSerializer(
allowed_msgpack_modules=[("tests.test_jsonplus", "MyPydantic")]
)
+349 -3
View File
@@ -6,19 +6,32 @@ from langchain_core.runnables import RunnableConfig
from pydantic import BaseModel
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
Checkpoint,
CheckpointMetadata,
create_checkpoint,
empty_checkpoint,
)
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
from langgraph.checkpoint.serde.jsonplus import (
JsonPlusSerializer,
_warned_blocked_types,
_warned_unregistered_types,
)
class MemoryPydantic(BaseModel):
foo: str
@pytest.fixture(autouse=True)
def _reset_warned_types() -> None:
# Warning dedup state is process-global; reset per-test so each case sees
# a fresh slate and assertions about warning emission are stable.
_warned_unregistered_types.clear()
_warned_blocked_types.clear()
class TestMemorySaver:
@pytest.fixture(autouse=True)
def setup(self) -> None:
@@ -196,8 +209,6 @@ class TestMemorySaver:
async def test_memory_saver() -> None:
from langgraph.checkpoint.memory import InMemorySaver
memory_saver = InMemorySaver()
assert isinstance(memory_saver, InMemorySaver)
@@ -308,3 +319,338 @@ def test_memory_saver_with_allowlist_proxy_isolated() -> None:
assert direct is not None
expected = obj.model_dump() if hasattr(obj, "model_dump") else obj.dict()
assert direct.checkpoint["channel_values"]["foo"] == expected
class TestInMemorySaverDeltaChannel:
def test_load_blobs_omits_delta_channel(self) -> None:
"""_load_blobs omits delta channels (stored as 'empty'); reconstruction deferred."""
saver = InMemorySaver()
thread_id, ns, channel = "t1", "", "messages"
v1 = "00000000000000000000000000000001.0000000000000000"
saver.blobs[(thread_id, ns, channel, v1)] = ("empty", b"")
result = saver._load_blobs(thread_id, ns, {channel: v1})
assert channel not in result
def test_get_channel_writes_collects_ancestor_writes_only(self) -> None:
"""_get_channel_writes_history collects ancestor writes oldest→newest,
and excludes writes stored at the target checkpoint itself (those are
pending writes for the next step, applied separately by pregel)."""
saver = InMemorySaver()
serde = JsonPlusSerializer()
thread_id, ns, channel = "t1", "", "messages"
cp1 = empty_checkpoint()
cp1["id"] = "cp1"
cp2 = empty_checkpoint()
cp2["id"] = "cp2"
saver.storage[thread_id][ns] = {
"cp1": (serde.dumps_typed(cp1), serde.dumps_typed({}), None),
"cp2": (serde.dumps_typed(cp2), serde.dumps_typed({}), "cp1"),
}
# Writes stored at cp1 produced the cp1 snapshot; part of history.
saver.writes[(thread_id, ns, "cp1")][("task1", 0)] = (
"task1",
channel,
serde.dumps_typed({"content": "hi"}),
"",
)
# Writes stored at cp2 are pending — they will produce cp3 when the
# step that loaded cp2 completes. They MUST NOT appear in the
# reconstructed snapshot value at cp2.
saver.writes[(thread_id, ns, "cp2")][("task2", 0)] = (
"task2",
channel,
serde.dumps_typed({"content": "pending"}),
"",
)
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": "cp2",
}
}
result = saver._get_channel_writes_history(config, channel)
assert result.seed is DELTA_SENTINEL
values = [v for _, _, v in result.writes]
assert values == [{"content": "hi"}]
def test_get_channel_writes_at_root_returns_empty(self) -> None:
"""Reconstructing the root checkpoint's state: no ancestors → []."""
saver = InMemorySaver()
serde = JsonPlusSerializer()
thread_id, ns, channel = "t1", "", "messages"
cp1 = empty_checkpoint()
cp1["id"] = "cp1"
saver.storage[thread_id][ns] = {
"cp1": (serde.dumps_typed(cp1), serde.dumps_typed({}), None),
}
saver.writes[(thread_id, ns, "cp1")][("task1", 0)] = (
"task1",
channel,
serde.dumps_typed({"content": "pending"}),
"",
)
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": "cp1",
}
}
result = saver._get_channel_writes_history(config, channel)
assert result.seed is DELTA_SENTINEL
assert result.writes == []
class TestBaseFallbackGetChannelWrites:
"""Exercises the `BaseCheckpointSaver._get_channel_writes_history` default
implementation — the path third-party savers inherit when they don't
override `_get_channel_writes_history` themselves.
Regression guard for a bug where the fallback passed the caller's config
(with `checkpoint_id`) straight to `self.list()`, which most savers
collapse to a single row — causing the fallback to return `[]`.
"""
def _build_saver_with_chain(self) -> tuple[InMemorySaver, str, str]:
"""Build an InMemorySaver with a 3-checkpoint chain and per-step writes
for a `messages` channel.
Returns `(saver, thread_id, namespace)`. The saver subclass deletes the
InMemorySaver override so the base class fallback is exercised.
"""
class _ThirdPartyStyleSaver(InMemorySaver):
_get_channel_writes_history = (
InMemorySaver.__mro__[1]._get_channel_writes_history # type: ignore[attr-defined]
)
_aget_channel_writes_history = (
InMemorySaver.__mro__[1]._aget_channel_writes_history # type: ignore[attr-defined]
)
saver = _ThirdPartyStyleSaver()
serde = JsonPlusSerializer()
thread_id, ns, channel = "t1", "", "messages"
cp0 = empty_checkpoint()
cp0["id"] = "00000000000000000000000000000001.0000000000000000"
cp1 = empty_checkpoint()
cp1["id"] = "00000000000000000000000000000002.0000000000000000"
cp2 = empty_checkpoint()
cp2["id"] = "00000000000000000000000000000003.0000000000000000"
saver.storage[thread_id][ns] = {
cp0["id"]: (serde.dumps_typed(cp0), serde.dumps_typed({}), None),
cp1["id"]: (serde.dumps_typed(cp1), serde.dumps_typed({}), cp0["id"]),
cp2["id"]: (serde.dumps_typed(cp2), serde.dumps_typed({}), cp1["id"]),
}
# Writes under cp0 produced cp1's state; writes under cp1 produced cp2's.
saver.writes[(thread_id, ns, cp0["id"])][("task1", 0)] = (
"task1",
channel,
serde.dumps_typed({"content": "first"}),
"",
)
saver.writes[(thread_id, ns, cp1["id"])][("task2", 0)] = (
"task2",
channel,
serde.dumps_typed({"content": "second"}),
"",
)
return saver, thread_id, ns
def test_fallback_returns_ancestor_writes_oldest_first(self) -> None:
saver, thread_id, ns = self._build_saver_with_chain()
target_id = "00000000000000000000000000000003.0000000000000000"
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": target_id,
}
}
result = saver._get_channel_writes_history(config, "messages")
assert result.seed is DELTA_SENTINEL
values = [v for _, _, v in result.writes]
assert values == [{"content": "first"}, {"content": "second"}]
async def test_async_fallback_returns_ancestor_writes_oldest_first(self) -> None:
saver, thread_id, ns = self._build_saver_with_chain()
target_id = "00000000000000000000000000000003.0000000000000000"
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": target_id,
}
}
result = await saver._aget_channel_writes_history(config, "messages")
assert result.seed is DELTA_SENTINEL
values = [v for _, _, v in result.writes]
assert values == [{"content": "first"}, {"content": "second"}]
async def test_async_fallback_concurrent_tasks_do_not_interfere(self) -> None:
"""Regression: the re-entrancy guard must be task-local, not thread-local.
Two concurrent `_aget_channel_writes_history` calls on the same
event-loop thread must each see their full reconstructed writes. A
`threading.local()` guard would let whichever task set it first
short-circuit the other to `writes=[]`.
"""
import asyncio
saver, thread_id, ns = self._build_saver_with_chain()
# Force the two tasks to interleave across the `set(True)` boundary:
# each `aget_tuple` yields control, so if the guard were thread-local
# the second task would observe `active=True` set by the first.
orig_aget_tuple = saver.aget_tuple
async def slow_aget_tuple(config: RunnableConfig) -> Any:
await asyncio.sleep(0)
return await orig_aget_tuple(config)
saver.aget_tuple = slow_aget_tuple # type: ignore[method-assign]
target_id = "00000000000000000000000000000003.0000000000000000"
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": target_id,
}
}
results = await asyncio.gather(
saver._aget_channel_writes_history(config, "messages"),
saver._aget_channel_writes_history(config, "messages"),
)
expected_values = [{"content": "first"}, {"content": "second"}]
for result in results:
assert result.seed is DELTA_SENTINEL
values = [v for _, _, v in result.writes]
assert values == expected_values
class TestPreDeltaBlobTerminator:
"""Verify the pre-delta blob terminator: when the ancestor walk hits a
checkpoint whose blob for the channel is a real value (not
DELTA_SENTINEL), reconstruction seeds from it and stops. This guards
* back-compat: a thread written by pre-delta code, then extended under
delta — reconstruction must return the correct value without walking
past the last pre-delta ancestor;
* perf: without the terminator, every reconstruct-after-migration would
walk all the way to the thread root.
"""
def _build_mixed_thread(self) -> tuple[InMemorySaver, str, str, str, str]:
"""Three-checkpoint chain: cp1 (pre-delta, blob=[A]), cp2 (delta,
write=B), cp3 (delta, write=C). Reconstructing at cp3 must yield
seed=[A] + writes=[B, C].
Returns `(saver, thread_id, ns, channel, cp3_id)`.
"""
saver = InMemorySaver()
serde = JsonPlusSerializer()
thread_id, ns, channel = "t1", "", "messages"
v1 = "00000000000000000000000000000001.0"
v2 = "00000000000000000000000000000002.0"
v3 = "00000000000000000000000000000003.0"
# Pre-delta: cp1 stored a real blob for the channel.
saver.blobs[(thread_id, ns, channel, v1)] = serde.dumps_typed(["A"])
# Delta-era: cp2 and cp3 store "empty"; real writes in checkpoint_writes.
saver.blobs[(thread_id, ns, channel, v2)] = ("empty", b"")
saver.blobs[(thread_id, ns, channel, v3)] = ("empty", b"")
cp1 = empty_checkpoint()
cp1["id"] = "cp1"
cp1["channel_versions"][channel] = v1
cp2 = empty_checkpoint()
cp2["id"] = "cp2"
cp2["channel_versions"][channel] = v2
cp3 = empty_checkpoint()
cp3["id"] = "cp3"
cp3["channel_versions"][channel] = v3
saver.storage[thread_id][ns] = {
"cp1": (serde.dumps_typed(cp1), serde.dumps_typed({}), None),
"cp2": (serde.dumps_typed(cp2), serde.dumps_typed({}), "cp1"),
"cp3": (serde.dumps_typed(cp3), serde.dumps_typed({}), "cp2"),
}
# Write under cp1 would be from the pre-delta era and MUST be ignored
# (the blob already captures it). We add one and assert it is not
# folded into the reconstructed result.
saver.writes[(thread_id, ns, "cp1")][("task0", 0)] = (
"task0",
channel,
serde.dumps_typed("PRE-DELTA-WRITE"),
"",
)
saver.writes[(thread_id, ns, "cp2")][("task2", 0)] = (
"task2",
channel,
serde.dumps_typed("B"),
"",
)
saver.writes[(thread_id, ns, "cp3")][("task3", 0)] = (
"task3",
channel,
serde.dumps_typed("PENDING-AT-TARGET"),
"",
)
return saver, thread_id, ns, channel, "cp3"
def test_seed_from_pre_delta_ancestor_blob(self) -> None:
saver, thread_id, ns, channel, target = self._build_mixed_thread()
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": target,
}
}
result = saver._get_channel_writes_history(config, channel)
# Seed came from the pre-delta blob at cp1.
assert result.seed == ["A"]
# Delta-era writes from cp2 replay through the reducer on top of seed.
# cp3 is the target — its own write is pending for the NEXT step and
# must be excluded.
values = [v for _, _, v in result.writes]
assert values == ["B"]
def test_pre_delta_blob_terminates_walk_before_older_writes(self) -> None:
"""Writes stored at the pre-delta ancestor itself must not be replayed
(the blob subsumes them)."""
saver, thread_id, ns, channel, target = self._build_mixed_thread()
config: RunnableConfig = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": ns,
"checkpoint_id": target,
}
}
result = saver._get_channel_writes_history(config, channel)
values = [v for _, _, v in result.writes]
# The pre-delta write under cp1 must not appear (the blob subsumes it).
assert "PRE-DELTA-WRITE" not in values
# And the pending write at the target is never folded in.
assert "PENDING-AT-TARGET" not in values
+129 -10
View File
@@ -267,7 +267,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.23"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -279,14 +279,14 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1d/47/a5f21b651e9cbd7a26c3e5809336d10a0be94ef7bdf6bea47f2ad9fff1a8/langchain_core-1.2.23.tar.gz", hash = "sha256:fdec64f90cfea25317e88d9803c44684af1f4e30dec4e58320dd7393bb0f0785", size = 841684, upload-time = "2026-03-27T23:28:14.6Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/5a/6ff2d76618e4cac531ea51d4ef44c6add36575a84c3f0f8877aee68c951a/langchain_core-1.2.23-py3-none-any.whl", hash = "sha256:70866dfc5275b7840ce272ff70f0ff216c8666ab25dc1b41964a4ef58c02a3ff", size = 506709, upload-time = "2026-03-27T23:28:13.372Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
name = "langgraph-checkpoint"
version = "4.0.1"
version = "4.1.0a3"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -369,7 +369,7 @@ test = [
[[package]]
name = "langsmith"
version = "0.6.4"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -379,11 +379,12 @@ dependencies = [
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "uuid-utils" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/85/9c7933052a997da1b85bc5c774f3865e9b1da1c8d71541ea133178b13229/langsmith-0.6.4.tar.gz", hash = "sha256:36f7223a01c218079fbb17da5e536ebbaf5c1468c028abe070aa3ae59bc99ec8", size = 919964, upload-time = "2026-01-15T20:02:28.873Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/0f/09a6637a7ba777eb307b7c80852d9ee26438e2bdafbad6fcc849ff9d9192/langsmith-0.6.4-py3-none-any.whl", hash = "sha256:ac4835860160be371042c7adbba3cb267bcf8d96a5ea976c33a8a4acad6c5486", size = 283503, upload-time = "2026-01-15T20:02:26.662Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
@@ -1117,7 +1118,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.0.2"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1128,9 +1129,9 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
@@ -1515,6 +1516,124 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]
[[package]]
name = "xxhash"
version = "3.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/ee/f9f1d656ad168681bb0f6b092372c1e533c4416b8069b1896a175c46e484/xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71", size = 32845, upload-time = "2025-10-02T14:33:51.573Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b1/93508d9460b292c74a09b83d16750c52a0ead89c51eea9951cb97a60d959/xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d", size = 30807, upload-time = "2025-10-02T14:33:52.964Z" },
{ url = "https://files.pythonhosted.org/packages/07/55/28c93a3662f2d200c70704efe74aab9640e824f8ce330d8d3943bf7c9b3c/xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8", size = 193786, upload-time = "2025-10-02T14:33:54.272Z" },
{ url = "https://files.pythonhosted.org/packages/c1/96/fec0be9bb4b8f5d9c57d76380a366f31a1781fb802f76fc7cda6c84893c7/xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058", size = 212830, upload-time = "2025-10-02T14:33:55.706Z" },
{ url = "https://files.pythonhosted.org/packages/c4/a0/c706845ba77b9611f81fd2e93fad9859346b026e8445e76f8c6fd057cc6d/xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2", size = 211606, upload-time = "2025-10-02T14:33:57.133Z" },
{ url = "https://files.pythonhosted.org/packages/67/1e/164126a2999e5045f04a69257eea946c0dc3e86541b400d4385d646b53d7/xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc", size = 444872, upload-time = "2025-10-02T14:33:58.446Z" },
{ url = "https://files.pythonhosted.org/packages/2d/4b/55ab404c56cd70a2cf5ecfe484838865d0fea5627365c6c8ca156bd09c8f/xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc", size = 193217, upload-time = "2025-10-02T14:33:59.724Z" },
{ url = "https://files.pythonhosted.org/packages/45/e6/52abf06bac316db33aa269091ae7311bd53cfc6f4b120ae77bac1b348091/xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07", size = 210139, upload-time = "2025-10-02T14:34:02.041Z" },
{ url = "https://files.pythonhosted.org/packages/34/37/db94d490b8691236d356bc249c08819cbcef9273a1a30acf1254ff9ce157/xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4", size = 197669, upload-time = "2025-10-02T14:34:03.664Z" },
{ url = "https://files.pythonhosted.org/packages/b7/36/c4f219ef4a17a4f7a64ed3569bc2b5a9c8311abdb22249ac96093625b1a4/xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06", size = 210018, upload-time = "2025-10-02T14:34:05.325Z" },
{ url = "https://files.pythonhosted.org/packages/fd/06/bfac889a374fc2fc439a69223d1750eed2e18a7db8514737ab630534fa08/xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4", size = 413058, upload-time = "2025-10-02T14:34:06.925Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d1/555d8447e0dd32ad0930a249a522bb2e289f0d08b6b16204cfa42c1f5a0c/xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b", size = 190628, upload-time = "2025-10-02T14:34:08.669Z" },
{ url = "https://files.pythonhosted.org/packages/d1/15/8751330b5186cedc4ed4b597989882ea05e0408b53fa47bcb46a6125bfc6/xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b", size = 30577, upload-time = "2025-10-02T14:34:10.234Z" },
{ url = "https://files.pythonhosted.org/packages/bb/cc/53f87e8b5871a6eb2ff7e89c48c66093bda2be52315a8161ddc54ea550c4/xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb", size = 31487, upload-time = "2025-10-02T14:34:11.618Z" },
{ url = "https://files.pythonhosted.org/packages/9f/00/60f9ea3bb697667a14314d7269956f58bf56bb73864f8f8d52a3c2535e9a/xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d", size = 27863, upload-time = "2025-10-02T14:34:12.619Z" },
{ url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" },
{ url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" },
{ url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" },
{ url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" },
{ url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" },
{ url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" },
{ url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" },
{ url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" },
{ url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" },
{ url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" },
{ url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" },
{ url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" },
{ url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" },
{ url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" },
{ url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" },
{ url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" },
{ url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" },
{ url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" },
{ url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" },
{ url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" },
{ url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" },
{ url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" },
{ url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" },
{ url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" },
{ url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" },
{ url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" },
{ url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" },
{ url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" },
{ url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" },
{ url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" },
{ url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" },
{ url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" },
{ url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" },
{ url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" },
{ url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" },
{ url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" },
{ url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" },
{ url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" },
{ url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" },
{ url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" },
{ url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" },
{ url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" },
{ url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" },
{ url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" },
{ url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" },
{ url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" },
{ url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" },
{ url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" },
{ url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" },
{ url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" },
{ url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" },
{ url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" },
{ url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" },
{ url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" },
{ url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" },
{ url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" },
{ url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" },
{ url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" },
{ url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" },
{ url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" },
{ url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" },
{ url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" },
{ url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" },
{ url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" },
{ url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" },
{ url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" },
{ url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" },
{ url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" },
{ url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" },
{ url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" },
{ url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" },
{ url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" },
{ url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" },
{ url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" },
{ url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" },
{ url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" },
{ url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" },
{ url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" },
{ url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" },
{ url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" },
{ url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" },
{ url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" },
{ url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" },
{ url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" },
]
[[package]]
name = "zstandard"
version = "0.25.0"
@@ -5,5 +5,5 @@ description = "Test for prerelease stuff"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langchain-openai==1.0.1"
"langchain-openai==1.1.14"
]
@@ -5,7 +5,7 @@ description = "Test for prerelease stuff"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langchain-openai==1.0.0a2",
"langchain-openai==1.1.14",
"langchain-anthropic==1.0.0a5",
"langgraph==1.1.5"
]
@@ -5,7 +5,7 @@ description = "Test for prerelease stuff"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langchain-openai==1.0.0a2",
"langchain-openai==1.1.14",
"langgraph==1.1.2",
"langchain_community>=0.3.0",
]
+8 -8
View File
@@ -21,25 +21,25 @@
"test:all": "yarn test && yarn test:int && yarn lint:langgraph"
},
"dependencies": {
"@langchain/core": "^1.1.38",
"@langchain/langgraph": "^1.2.6"
"@langchain/core": "^1.1.42",
"@langchain/langgraph": "^1.2.9"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@tsconfig/recommended": "^1.0.13",
"@types/jest": "^30.0.0",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"dotenv": "^17.3.1",
"eslint": "^10.1.0",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"dotenv": "^17.4.2",
"eslint": "^10.2.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^5.5.5",
"jest": "^30.3.0",
"prettier": "^3.8.1",
"ts-jest": "^29.4.6",
"prettier": "^3.8.3",
"ts-jest": "^29.4.9",
"typescript": "^5.9.3"
}
}
+130 -157
View File
@@ -474,26 +474,26 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
"@eslint/config-array@^0.23.3":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.3.tgz#3f4a93dd546169c09130cbd10f2415b13a20a219"
integrity sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==
"@eslint/config-array@^0.23.5":
version "0.23.5"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95"
integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==
dependencies:
"@eslint/object-schema" "^3.0.3"
"@eslint/object-schema" "^3.0.5"
debug "^4.3.1"
minimatch "^10.2.4"
"@eslint/config-helpers@^0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.3.tgz#721fe6bbb90d74b0c80d6ff2428e5bbcb002becb"
integrity sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==
"@eslint/config-helpers@^0.5.5":
version "0.5.5"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.5.tgz#ae16134e4792ac5fbdc533548a24ac1ea9f7f3ae"
integrity sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==
dependencies:
"@eslint/core" "^1.1.1"
"@eslint/core" "^1.2.1"
"@eslint/core@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.1.tgz#450f3d2be2d463ccd51119544092256b4e88df32"
integrity sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==
"@eslint/core@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce"
integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==
dependencies:
"@types/json-schema" "^7.0.15"
@@ -517,17 +517,17 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583"
integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==
"@eslint/object-schema@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.3.tgz#5bf671e52e382e4adc47a9906f2699374637db6b"
integrity sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==
"@eslint/object-schema@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091"
integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==
"@eslint/plugin-kit@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz#eb9e6689b56ce8bc1855bb33090e63f3fc115e8e"
integrity sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==
"@eslint/plugin-kit@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz#c4125fd015eceeb09b793109fdbcd4dd0a02d346"
integrity sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==
dependencies:
"@eslint/core" "^1.1.1"
"@eslint/core" "^1.2.1"
levn "^0.4.1"
"@humanfs/core@^0.19.1":
@@ -890,10 +890,10 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@langchain/core@^1.1.38":
version "1.1.38"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.1.38.tgz#b5939b2e77af408771c9bb95bf56a5ad8183313e"
integrity sha512-C340wH1YL10CiVOFlEpQMp0zQE85/eBLKX/gi1Lv7shAyUmR3CQ0t/mXlCd5RsZa6ntAN1kDJnp64ArWey9XAA==
"@langchain/core@^1.1.42":
version "1.1.42"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.1.42.tgz#607bfd34c1414c9cbeb2859fda1b254a14c28085"
integrity sha512-d0tN96BrwPMryYyWR9VfyAntSivn7EQrZCe5Kpxum93tcjTXbKKmKvItFec8AluQt88iTcmAJrahUZUNfzGwTA==
dependencies:
"@cfworker/json-schema" "^4.0.2"
"@standard-schema/spec" "^1.1.0"
@@ -904,7 +904,6 @@
langsmith ">=0.5.0 <1.0.0"
mustache "^4.2.0"
p-queue "^6.6.2"
uuid "^11.1.0"
zod "^3.25.76 || ^4"
"@langchain/langgraph-checkpoint@^1.0.1":
@@ -914,23 +913,23 @@
dependencies:
uuid "^10.0.0"
"@langchain/langgraph-sdk@~1.8.1":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.8.3.tgz#deedc9bddd863693c6ab5f3a74058548b040377d"
integrity sha512-Py0S5yVtlOHz410aEsSGLRKjtsK2giDvfPS1JjAjTdcs71khuJufFtUZFwmwdJCbsG4DaGurRLHOAJu9jO4a0g==
"@langchain/langgraph-sdk@~1.8.9":
version "1.8.10"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.8.10.tgz#bd175cda1ab629845ef28b22b45f3bdd1f65b029"
integrity sha512-wrB3rkRw5KAmsqezwvKP3midT4qJrV6Hj9XJMYo+cbvXC4HYpSAmyY/VriSyeTFRbLG/OP/pY2Yz+9Z54nSaXQ==
dependencies:
"@types/json-schema" "^7.0.15"
p-queue "^9.0.1"
p-retry "^7.1.1"
uuid "^13.0.0"
"@langchain/langgraph@^1.2.6":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.2.6.tgz#e6a578f96fbbc1c54ba78e0298884671dad5d195"
integrity sha512-5cX402dNGN6w9+0mlMU2dgUiKx6Y1tlENp7x05e9ByDbQCHSDc0kyqRWNFLGc7vatQ92S4ylxQrcCJvi8Fr4SQ==
"@langchain/langgraph@^1.2.9":
version "1.2.9"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.2.9.tgz#4f0ba159062c3649c83cf5f621ef926b96933e32"
integrity sha512-3c7BtGycHC2v9p6w/Hv8L7kEl1YnZYOQTDJtmAp3knk6JOedO7d2bYP3y0SRyhv5orUEGf/KGvx8ZsB/ideP7g==
dependencies:
"@langchain/langgraph-checkpoint" "^1.0.1"
"@langchain/langgraph-sdk" "~1.8.1"
"@langchain/langgraph-sdk" "~1.8.9"
"@standard-schema/spec" "1.1.0"
uuid "^10.0.0"
@@ -1086,11 +1085,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
"@types/uuid@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d"
integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -1103,100 +1097,100 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz#ad40e492f1931f46da1bd888e52b9e56df9063aa"
integrity sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==
"@typescript-eslint/eslint-plugin@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz#781bc6f9002982cfaf75a185240e24ad7276628a"
integrity sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/type-utils" "8.58.0"
"@typescript-eslint/utils" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/type-utils" "8.59.1"
"@typescript-eslint/utils" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.58.0.tgz#da04ece1967b6c2fe8f10c3473dabf3825795ef7"
integrity sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==
"@typescript-eslint/parser@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.59.1.tgz#835d20a62350659a082a1ae2a60b822c40488905"
integrity sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==
dependencies:
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.58.0.tgz#66ceda0aabf7427aec3e2713fa43eb278dead2aa"
integrity sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==
"@typescript-eslint/project-service@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.59.1.tgz#49efe87c37ef84262f23df8bf62fdc56698ca6fe"
integrity sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.58.0"
"@typescript-eslint/types" "^8.58.0"
"@typescript-eslint/tsconfig-utils" "^8.59.1"
"@typescript-eslint/types" "^8.59.1"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz#e304142775e49a1b7ac3c8bf2536714447c72cab"
integrity sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==
"@typescript-eslint/scope-manager@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz#ed90d054fc3db2d0c81464db3a953a94fb85bb58"
integrity sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
"@typescript-eslint/tsconfig-utils@8.58.0", "@typescript-eslint/tsconfig-utils@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz#c5a8edb21f31e0fdee565724e1b984171c559482"
integrity sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==
"@typescript-eslint/tsconfig-utils@8.59.1", "@typescript-eslint/tsconfig-utils@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz#ba2a779a444f1d5cb92a606f9b209d239fd4cab1"
integrity sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==
"@typescript-eslint/type-utils@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz#ce0e72cd967ffbbe8de322db6089bd4374be352f"
integrity sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==
"@typescript-eslint/type-utils@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz#9c83d3f2ed9187a815e8120f72c08317e513e409"
integrity sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/utils" "8.58.0"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/utils" "8.59.1"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.58.0", "@typescript-eslint/types@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.58.0.tgz#e94ae7abdc1c6530e71183c1007b61fa93112a5a"
integrity sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==
"@typescript-eslint/types@8.59.1", "@typescript-eslint/types@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.59.1.tgz#c1d014d3f03a97e0113a8899fc9d4e45a7fb0ca9"
integrity sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==
"@typescript-eslint/typescript-estree@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz#ed233faa8e2f2a2e1357c3e7d553d6465a0ee59a"
integrity sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==
"@typescript-eslint/typescript-estree@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz#4391fadf98a22c869c5b6522dbf4e491e53e351a"
integrity sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==
dependencies:
"@typescript-eslint/project-service" "8.58.0"
"@typescript-eslint/tsconfig-utils" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/project-service" "8.59.1"
"@typescript-eslint/tsconfig-utils" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.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.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.58.0.tgz#21a74a7963b0d288b719a4121c7dd555adaab3c3"
integrity sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==
"@typescript-eslint/utils@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.59.1.tgz#cf6204d69701bbbc5b150f98c18aeef0a42c10bd"
integrity sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/visitor-keys@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz#2abd55a4be70fd55967aceaba4330b9ba9f45189"
integrity sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==
"@typescript-eslint/visitor-keys@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz#b5cba576287a3eeb0b400b62813189abcc3f976a"
integrity sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/types" "8.59.1"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.3.0":
@@ -1782,13 +1776,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
console-table-printer@^2.12.1:
version "2.15.0"
resolved "https://registry.yarnpkg.com/console-table-printer/-/console-table-printer-2.15.0.tgz#5c808204640b8f024d545bde8aabe5d344dfadc1"
integrity sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==
dependencies:
simple-wcswidth "^1.1.2"
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@@ -1921,10 +1908,10 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
dotenv@^17.3.1:
version "17.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.3.1.tgz#2706f5b0165e45a1503348187b8468f87fe6aae2"
integrity sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==
dotenv@^17.4.2:
version "17.4.2"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.4.2.tgz#c07e54a746e11eba021dd9e1047ced5afdc1c034"
integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==
dunder-proto@^1.0.0, dunder-proto@^1.0.1:
version "1.0.1"
@@ -2275,17 +2262,17 @@ 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.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.1.0.tgz#9ca98e654e642ab2e1af6d1e9d8613857ac341b4"
integrity sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==
eslint@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.2.1.tgz#224b2a6caeb34473eddcf918762363e2e063222a"
integrity sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.3"
"@eslint/config-helpers" "^0.5.3"
"@eslint/core" "^1.1.1"
"@eslint/plugin-kit" "^0.6.1"
"@eslint/config-array" "^0.23.5"
"@eslint/config-helpers" "^0.5.5"
"@eslint/core" "^1.2.1"
"@eslint/plugin-kit" "^0.7.1"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
@@ -2695,7 +2682,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.8:
handlebars@^4.7.9:
version "4.7.9"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f"
integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==
@@ -3688,16 +3675,12 @@ keyv@^4.5.4:
json-buffer "3.0.1"
"langsmith@>=0.5.0 <1.0.0":
version "0.5.4"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.4.tgz#f75b82b08e30db72a7d1d595b341e9666bd525e5"
integrity sha512-qYkNIoKpf0ZYt+cYzrDV+XI3FCexApmZmp8EMs3eDTMv0OvrHMLoxJ9IpkeoXJSX24+GPk0/jXjKx2hWerpy9w==
version "0.5.20"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.20.tgz#4021847d2ccd5a86c5eb96060f9bb5f19f80eca5"
integrity sha512-ULhLM8RswvQDXufLtNtvclHrWCBx8Cb5UPI6lAZC+8Dq59iHsVPz/3Ac9khWNm1VIvChRsuykixD/WrmzuuA3Q==
dependencies:
"@types/uuid" "^10.0.0"
chalk "^4.1.2"
console-table-printer "^2.12.1"
p-queue "^6.6.2"
semver "^7.6.3"
uuid "^10.0.0"
p-queue "6.6.2"
uuid "10.0.0"
leven@^3.1.0:
version "3.1.0"
@@ -4007,7 +3990,7 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
p-queue@^6.6.2:
p-queue@6.6.2, p-queue@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
@@ -4146,10 +4129,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
prettier@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0"
integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==
pretty-format@30.2.0, pretty-format@^30.0.0:
version "30.2.0"
@@ -4303,7 +4286,7 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3:
semver@^7.5.3, semver@^7.5.4, semver@^7.7.2, semver@^7.7.3, semver@^7.7.4:
version "7.7.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
@@ -4411,11 +4394,6 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
simple-wcswidth@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz#66722f37629d5203f9b47c5477b1225b85d6525b"
integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -4644,18 +4622,18 @@ 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.6:
version "29.4.6"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9"
integrity sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==
ts-jest@^29.4.9:
version "29.4.9"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.9.tgz#47dc33d0f5c36bddcedd16afefae285e0b049d2d"
integrity sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==
dependencies:
bs-logger "^0.2.6"
fast-json-stable-stringify "^2.1.0"
handlebars "^4.7.8"
handlebars "^4.7.9"
json5 "^2.2.3"
lodash.memoize "^4.1.2"
make-error "^1.3.6"
semver "^7.7.3"
semver "^7.7.4"
type-fest "^4.41.0"
yargs-parser "^21.1.1"
@@ -4870,16 +4848,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
uuid@^10.0.0:
uuid@10.0.0, uuid@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
uuid@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
uuid@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8"
@@ -9,8 +9,8 @@
},
"dependencies": {
"@js-monorepo-example/shared": "*",
"@langchain/core": "^1.1.38",
"@langchain/langgraph": "^1.2.6"
"@langchain/core": "^1.1.42",
"@langchain/langgraph": "^1.2.9"
},
"devDependencies": {
"typescript": "^5.9.3"
+5 -5
View File
@@ -17,18 +17,18 @@
"lint": "eslint 'apps/**/*.ts' 'libs/**/*.ts'"
},
"devDependencies": {
"turbo": "^2.9.3",
"turbo": "^2.9.7",
"typescript": "^5.9.3",
"@tsconfig/recommended": "^1.0.13",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"eslint": "^10.1.0",
"eslint": "^10.2.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^5.5.5",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"prettier": "^3.8.1"
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"prettier": "^3.8.3"
}
}
+152 -223
View File
@@ -19,26 +19,26 @@
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
"@eslint/config-array@^0.23.3":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.3.tgz#3f4a93dd546169c09130cbd10f2415b13a20a219"
integrity sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==
"@eslint/config-array@^0.23.5":
version "0.23.5"
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.5.tgz#56e86d243049195d8acc0c06a1b3dfdc3fa3de95"
integrity sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==
dependencies:
"@eslint/object-schema" "^3.0.3"
"@eslint/object-schema" "^3.0.5"
debug "^4.3.1"
minimatch "^10.2.4"
"@eslint/config-helpers@^0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.3.tgz#721fe6bbb90d74b0c80d6ff2428e5bbcb002becb"
integrity sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==
"@eslint/config-helpers@^0.5.5":
version "0.5.5"
resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.5.tgz#ae16134e4792ac5fbdc533548a24ac1ea9f7f3ae"
integrity sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==
dependencies:
"@eslint/core" "^1.1.1"
"@eslint/core" "^1.2.1"
"@eslint/core@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.1.tgz#450f3d2be2d463ccd51119544092256b4e88df32"
integrity sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==
"@eslint/core@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.1.tgz#c1da7cd1b82fa8787f98b5629fb811848a1b63ce"
integrity sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==
dependencies:
"@types/json-schema" "^7.0.15"
@@ -62,17 +62,17 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583"
integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==
"@eslint/object-schema@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.3.tgz#5bf671e52e382e4adc47a9906f2699374637db6b"
integrity sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==
"@eslint/object-schema@^3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.5.tgz#88e9bf4d11d2b19c082e78ebe7ce88724a5eb091"
integrity sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==
"@eslint/plugin-kit@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz#eb9e6689b56ce8bc1855bb33090e63f3fc115e8e"
integrity sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==
"@eslint/plugin-kit@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz#c4125fd015eceeb09b793109fdbcd4dd0a02d346"
integrity sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==
dependencies:
"@eslint/core" "^1.1.1"
"@eslint/core" "^1.2.1"
levn "^0.4.1"
"@humanfs/core@^0.19.1":
@@ -103,10 +103,10 @@
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-9.0.0.tgz#4d0a3f127058043bf2e7ee169eaf30ed901302f3"
integrity sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==
"@langchain/core@^1.1.38":
version "1.1.38"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.1.38.tgz#b5939b2e77af408771c9bb95bf56a5ad8183313e"
integrity sha512-C340wH1YL10CiVOFlEpQMp0zQE85/eBLKX/gi1Lv7shAyUmR3CQ0t/mXlCd5RsZa6ntAN1kDJnp64ArWey9XAA==
"@langchain/core@^1.1.42":
version "1.1.42"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.1.42.tgz#607bfd34c1414c9cbeb2859fda1b254a14c28085"
integrity sha512-d0tN96BrwPMryYyWR9VfyAntSivn7EQrZCe5Kpxum93tcjTXbKKmKvItFec8AluQt88iTcmAJrahUZUNfzGwTA==
dependencies:
"@cfworker/json-schema" "^4.0.2"
"@standard-schema/spec" "^1.1.0"
@@ -117,7 +117,6 @@
langsmith ">=0.5.0 <1.0.0"
mustache "^4.2.0"
p-queue "^6.6.2"
uuid "^11.1.0"
zod "^3.25.76 || ^4"
"@langchain/langgraph-checkpoint@^1.0.1":
@@ -127,23 +126,23 @@
dependencies:
uuid "^10.0.0"
"@langchain/langgraph-sdk@~1.8.1":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.8.3.tgz#deedc9bddd863693c6ab5f3a74058548b040377d"
integrity sha512-Py0S5yVtlOHz410aEsSGLRKjtsK2giDvfPS1JjAjTdcs71khuJufFtUZFwmwdJCbsG4DaGurRLHOAJu9jO4a0g==
"@langchain/langgraph-sdk@~1.8.9":
version "1.8.10"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.8.10.tgz#bd175cda1ab629845ef28b22b45f3bdd1f65b029"
integrity sha512-wrB3rkRw5KAmsqezwvKP3midT4qJrV6Hj9XJMYo+cbvXC4HYpSAmyY/VriSyeTFRbLG/OP/pY2Yz+9Z54nSaXQ==
dependencies:
"@types/json-schema" "^7.0.15"
p-queue "^9.0.1"
p-retry "^7.1.1"
uuid "^13.0.0"
"@langchain/langgraph@^1.2.6":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.2.6.tgz#e6a578f96fbbc1c54ba78e0298884671dad5d195"
integrity sha512-5cX402dNGN6w9+0mlMU2dgUiKx6Y1tlENp7x05e9ByDbQCHSDc0kyqRWNFLGc7vatQ92S4ylxQrcCJvi8Fr4SQ==
"@langchain/langgraph@^1.2.9":
version "1.2.9"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.2.9.tgz#4f0ba159062c3649c83cf5f621ef926b96933e32"
integrity sha512-3c7BtGycHC2v9p6w/Hv8L7kEl1YnZYOQTDJtmAp3knk6JOedO7d2bYP3y0SRyhv5orUEGf/KGvx8ZsB/ideP7g==
dependencies:
"@langchain/langgraph-checkpoint" "^1.0.1"
"@langchain/langgraph-sdk" "~1.8.1"
"@langchain/langgraph-sdk" "~1.8.9"
"@standard-schema/spec" "1.1.0"
uuid "^10.0.0"
@@ -167,35 +166,35 @@
resolved "https://registry.yarnpkg.com/@tsconfig/recommended/-/recommended-1.0.13.tgz#269fce3ad04ca70b93269ff44cca81b950f542da"
integrity sha512-sySRuBfMKyKO/j2ZAhR8kSembhjuPEV4Ra3AHtmWLq51+iGaudr45crPSzNC5b7/Ctrh9dfUpBuTlYrH6rM58Q==
"@turbo/darwin-64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/darwin-64/-/darwin-64-2.9.3.tgz#5d782d341ddebc20822d09ba1393238d4467ae7b"
integrity sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==
"@turbo/darwin-64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/darwin-64/-/darwin-64-2.9.7.tgz#46fddae01ea7817192dc6ff36a678cc3879b753b"
integrity sha512-wnvOWuVWJ5EUHNKxExEWiGlTeVpLG1L0PCu5MUozyC1P2SHGiWsmpW6/yAuShH91Fa2TAHOvdCRBzriZh4j4Eg==
"@turbo/darwin-arm64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/darwin-arm64/-/darwin-arm64-2.9.3.tgz#75cd662d9c2a572532ce82d7cae1a30e2497d693"
integrity sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==
"@turbo/darwin-arm64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/darwin-arm64/-/darwin-arm64-2.9.7.tgz#cd7cf3a024509af0f59ae3885216d62fd090fdb6"
integrity sha512-mA0FIPMwwN3lodDkQYaGxj6PeT7ZaN5aCEbkKn/WB+ZB9yJdVWA4J83GH7t43jqDc5dcnVluVN5UFx3plRiXhA==
"@turbo/linux-64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/linux-64/-/linux-64-2.9.3.tgz#de693a996071654a0bea51c7ed9dc6fdc3950ae8"
integrity sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==
"@turbo/linux-64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/linux-64/-/linux-64-2.9.7.tgz#25ee9a2cb3042498a3203e14653b14041105020f"
integrity sha512-fEbUYpgb5l7P+q+5tsWF2gw+/GSjUsuUTcnfm+f0lozUjgcjLKyOat6PgtAChmIFcTPchCL/8rJ3TvkBy01gfA==
"@turbo/linux-arm64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/linux-arm64/-/linux-arm64-2.9.3.tgz#88fc6fe572d83c5488eeae9b564b57c335f4d856"
integrity sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==
"@turbo/linux-arm64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/linux-arm64/-/linux-arm64-2.9.7.tgz#5b19d6249a50cb8153b8f2bc22ad10bbda614fbe"
integrity sha512-VkUjulo9ytfHKUHOS5gy0XPoh4CTKPXWCL8nLdrlHVi9fSut31ECeUqnm/dAbETP5D4xo9mH9XkJ+qMzGe/zmg==
"@turbo/windows-64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/windows-64/-/windows-64-2.9.3.tgz#a7812e4f33972f74daed93b2ac8845f88a68c775"
integrity sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==
"@turbo/windows-64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/windows-64/-/windows-64-2.9.7.tgz#900645776fa44ff8333e801d890992736f6dbc72"
integrity sha512-/GWdY6/x4aIHqkYJq596Rpdk1x0MkpRPkJcLAoB3yGRwyUms0+u2F1GnV54IbyAZTeKLRWSJKzNC+QwVGdYchA==
"@turbo/windows-arm64@2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@turbo/windows-arm64/-/windows-arm64-2.9.3.tgz#1891a89fe87d501e63a7a4367082f3afec18fa6f"
integrity sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==
"@turbo/windows-arm64@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@turbo/windows-arm64/-/windows-arm64-2.9.7.tgz#e678d73fdbcbb12a679403fe7ba070fb67f37810"
integrity sha512-xBBgxCC5PK2+WZ1PPRZdp+aJ0bMBcEbweXWux3RUHJvX9ZodcoQySkrW6qt+ahb+uk8ZjyQodLfDwtVSoYds1w==
"@types/esrecurse@^4.3.1":
version "4.3.1"
@@ -217,105 +216,100 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/uuid@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d"
integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==
"@typescript-eslint/eslint-plugin@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz#ad40e492f1931f46da1bd888e52b9e56df9063aa"
integrity sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==
"@typescript-eslint/eslint-plugin@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz#781bc6f9002982cfaf75a185240e24ad7276628a"
integrity sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/type-utils" "8.58.0"
"@typescript-eslint/utils" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/type-utils" "8.59.1"
"@typescript-eslint/utils" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.58.0.tgz#da04ece1967b6c2fe8f10c3473dabf3825795ef7"
integrity sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==
"@typescript-eslint/parser@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.59.1.tgz#835d20a62350659a082a1ae2a60b822c40488905"
integrity sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==
dependencies:
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.58.0.tgz#66ceda0aabf7427aec3e2713fa43eb278dead2aa"
integrity sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==
"@typescript-eslint/project-service@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.59.1.tgz#49efe87c37ef84262f23df8bf62fdc56698ca6fe"
integrity sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.58.0"
"@typescript-eslint/types" "^8.58.0"
"@typescript-eslint/tsconfig-utils" "^8.59.1"
"@typescript-eslint/types" "^8.59.1"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz#e304142775e49a1b7ac3c8bf2536714447c72cab"
integrity sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==
"@typescript-eslint/scope-manager@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz#ed90d054fc3db2d0c81464db3a953a94fb85bb58"
integrity sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.1"
"@typescript-eslint/tsconfig-utils@8.58.0", "@typescript-eslint/tsconfig-utils@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz#c5a8edb21f31e0fdee565724e1b984171c559482"
integrity sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==
"@typescript-eslint/tsconfig-utils@8.59.1", "@typescript-eslint/tsconfig-utils@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz#ba2a779a444f1d5cb92a606f9b209d239fd4cab1"
integrity sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==
"@typescript-eslint/type-utils@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz#ce0e72cd967ffbbe8de322db6089bd4374be352f"
integrity sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==
"@typescript-eslint/type-utils@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz#9c83d3f2ed9187a815e8120f72c08317e513e409"
integrity sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/utils" "8.58.0"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/utils" "8.59.1"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.58.0", "@typescript-eslint/types@^8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.58.0.tgz#e94ae7abdc1c6530e71183c1007b61fa93112a5a"
integrity sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==
"@typescript-eslint/types@8.59.1", "@typescript-eslint/types@^8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.59.1.tgz#c1d014d3f03a97e0113a8899fc9d4e45a7fb0ca9"
integrity sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==
"@typescript-eslint/typescript-estree@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz#ed233faa8e2f2a2e1357c3e7d553d6465a0ee59a"
integrity sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==
"@typescript-eslint/typescript-estree@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz#4391fadf98a22c869c5b6522dbf4e491e53e351a"
integrity sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==
dependencies:
"@typescript-eslint/project-service" "8.58.0"
"@typescript-eslint/tsconfig-utils" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/visitor-keys" "8.58.0"
"@typescript-eslint/project-service" "8.59.1"
"@typescript-eslint/tsconfig-utils" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/visitor-keys" "8.59.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.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.58.0.tgz#21a74a7963b0d288b719a4121c7dd555adaab3c3"
integrity sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==
"@typescript-eslint/utils@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.59.1.tgz#cf6204d69701bbbc5b150f98c18aeef0a42c10bd"
integrity sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.58.0"
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/typescript-estree" "8.58.0"
"@typescript-eslint/scope-manager" "8.59.1"
"@typescript-eslint/types" "8.59.1"
"@typescript-eslint/typescript-estree" "8.59.1"
"@typescript-eslint/visitor-keys@8.58.0":
version "8.58.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz#2abd55a4be70fd55967aceaba4330b9ba9f45189"
integrity sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==
"@typescript-eslint/visitor-keys@8.59.1":
version "8.59.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz#b5cba576287a3eeb0b400b62813189abcc3f976a"
integrity sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==
dependencies:
"@typescript-eslint/types" "8.58.0"
"@typescript-eslint/types" "8.59.1"
eslint-visitor-keys "^5.0.0"
acorn-jsx@^5.3.2:
@@ -343,13 +337,6 @@ ajv@^6.14.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
ansi-styles@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
@@ -508,38 +495,11 @@ camelcase@6:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
console-table-printer@^2.12.1:
version "2.14.6"
resolved "https://registry.yarnpkg.com/console-table-printer/-/console-table-printer-2.14.6.tgz#edfe0bf311fa2701922ed509443145ab51e06436"
integrity sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw==
dependencies:
simple-wcswidth "^1.0.1"
cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
@@ -826,17 +786,17 @@ 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.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.1.0.tgz#9ca98e654e642ab2e1af6d1e9d8613857ac341b4"
integrity sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==
eslint@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.2.1.tgz#224b2a6caeb34473eddcf918762363e2e063222a"
integrity sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
"@eslint/config-array" "^0.23.3"
"@eslint/config-helpers" "^0.5.3"
"@eslint/core" "^1.1.1"
"@eslint/plugin-kit" "^0.6.1"
"@eslint/config-array" "^0.23.5"
"@eslint/config-helpers" "^0.5.5"
"@eslint/core" "^1.2.1"
"@eslint/plugin-kit" "^0.7.1"
"@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1"
"@humanwhocodes/retry" "^0.4.2"
@@ -1059,11 +1019,6 @@ has-bigints@^1.0.2:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe"
integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
@@ -1372,16 +1327,12 @@ keyv@^4.5.4:
json-buffer "3.0.1"
"langsmith@>=0.5.0 <1.0.0":
version "0.5.4"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.4.tgz#f75b82b08e30db72a7d1d595b341e9666bd525e5"
integrity sha512-qYkNIoKpf0ZYt+cYzrDV+XI3FCexApmZmp8EMs3eDTMv0OvrHMLoxJ9IpkeoXJSX24+GPk0/jXjKx2hWerpy9w==
version "0.5.20"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.5.20.tgz#4021847d2ccd5a86c5eb96060f9bb5f19f80eca5"
integrity sha512-ULhLM8RswvQDXufLtNtvclHrWCBx8Cb5UPI6lAZC+8Dq59iHsVPz/3Ac9khWNm1VIvChRsuykixD/WrmzuuA3Q==
dependencies:
"@types/uuid" "^10.0.0"
chalk "^4.1.2"
console-table-printer "^2.12.1"
p-queue "^6.6.2"
semver "^7.6.3"
uuid "^10.0.0"
p-queue "6.6.2"
uuid "10.0.0"
levn@^0.4.1:
version "0.4.1"
@@ -1528,7 +1479,7 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
p-queue@^6.6.2:
p-queue@6.6.2, p-queue@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
@@ -1607,10 +1558,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
prettier@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0"
integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==
punycode@^2.1.0:
version "2.3.1"
@@ -1690,11 +1641,6 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.6.3:
version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
semver@^7.7.3:
version "7.7.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
@@ -1783,11 +1729,6 @@ side-channel@^1.1.0:
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
simple-wcswidth@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz#66722f37629d5203f9b47c5477b1225b85d6525b"
integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==
stop-iteration-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad"
@@ -1838,13 +1779,6 @@ strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -1880,17 +1814,17 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
turbo@^2.9.3:
version "2.9.3"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.9.3.tgz#4f0adf2c8677edd7b46d400629c99e9e26e58e44"
integrity sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==
turbo@^2.9.7:
version "2.9.7"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.9.7.tgz#da6d5a821f0bde5174e1a55af6690dad5c2fb004"
integrity sha512-epxzqVO2s0IxcSWcgb+qKrtco8isfe7g3VtiS6hkYnEK4A9XQDZbrtavQ6MtWR1KoQn+1fUomaQth2rfRHlUlg==
optionalDependencies:
"@turbo/darwin-64" "2.9.3"
"@turbo/darwin-arm64" "2.9.3"
"@turbo/linux-64" "2.9.3"
"@turbo/linux-arm64" "2.9.3"
"@turbo/windows-64" "2.9.3"
"@turbo/windows-arm64" "2.9.3"
"@turbo/darwin-64" "2.9.7"
"@turbo/darwin-arm64" "2.9.7"
"@turbo/linux-64" "2.9.7"
"@turbo/linux-arm64" "2.9.7"
"@turbo/windows-64" "2.9.7"
"@turbo/windows-arm64" "2.9.7"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
@@ -1966,16 +1900,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
uuid@^10.0.0:
uuid@10.0.0, uuid@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
uuid@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
uuid@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.21"
__version__ = "0.4.24"
+124
View File
@@ -0,0 +1,124 @@
"""Shared ignore-file handling for local source filtering."""
import pathlib
from dataclasses import dataclass
import pathspec
_ALWAYS_EXCLUDE = [
"__pycache__/",
".git/",
".venv/",
"venv/",
"node_modules/",
".tox/",
".mypy_cache/",
]
_ALWAYS_EXCLUDE_NAMES = frozenset(
pattern.rstrip("/").split("/")[-1] for pattern in _ALWAYS_EXCLUDE
)
_GLOB_CHARS = frozenset("*?[")
@dataclass(frozen=True, slots=True)
class _NegatedDockerignoreHints:
exact_dirs: frozenset[pathlib.PurePosixPath] = frozenset()
wildcard_prefixes: frozenset[pathlib.PurePosixPath] = frozenset()
recurse_all: bool = False
def requires_dir_walk(self, path: pathlib.PurePosixPath) -> bool:
if self.recurse_all or path in self.exact_dirs:
return True
return any(
path == prefix or path in prefix.parents or prefix in path.parents
for prefix in self.wildcard_prefixes
)
def _build_ignore_spec(
directory: pathlib.Path, *, include_gitignore: bool = True
) -> pathspec.PathSpec:
"""Build a PathSpec combining built-in exclusions with ignore files.
Always excludes common non-source directories (`_ALWAYS_EXCLUDE`). On top
of that, patterns from `.dockerignore` are merged in. `.gitignore` patterns
are optional because some callers need Docker build-context semantics,
while archive creation wants both files.
"""
lines: list[str] = list(_ALWAYS_EXCLUDE)
ignore_files = [".dockerignore"]
if include_gitignore:
ignore_files.append(".gitignore")
for name in ignore_files:
ignore_file = directory / name
if ignore_file.is_file():
lines.extend(ignore_file.read_text(encoding="utf-8").splitlines())
return pathspec.PathSpec.from_lines("gitwildmatch", lines)
def _is_always_excluded(path: pathlib.PurePosixPath, *, is_dir: bool) -> bool:
"""Whether `path` lives inside a built-in excluded directory."""
parent_parts = path.parts if is_dir else path.parts[:-1]
return any(part in _ALWAYS_EXCLUDE_NAMES for part in parent_parts)
def _build_dockerignore_negation_hints(
directory: pathlib.Path,
) -> _NegatedDockerignoreHints:
"""Summarize which ignored directories must still be traversed.
Most negations only require walking a small, concrete chain of parent
directories (for example `!assets/keep.txt` requires entering `assets/`).
Broader glob negations may force a wider walk.
"""
ignore_file = directory / ".dockerignore"
if not ignore_file.is_file():
return _NegatedDockerignoreHints()
exact_dirs: set[pathlib.PurePosixPath] = set()
wildcard_prefixes: set[pathlib.PurePosixPath] = set()
recurse_all = False
for raw_line in ignore_file.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or line.startswith("\\!"):
continue
if line.startswith("\\#"):
line = line[1:]
if not line.startswith("!"):
continue
pattern = line[1:].lstrip("/")
while pattern.startswith("./"):
pattern = pattern[2:]
pattern = pattern.rstrip("/")
parts = [part for part in pattern.split("/") if part and part != "."]
if not parts:
recurse_all = True
continue
wildcard_index = next(
(
idx
for idx, part in enumerate(parts)
if any(char in part for char in _GLOB_CHARS)
),
None,
)
if wildcard_index is not None:
literal_parts = parts[:wildcard_index]
if not literal_parts:
recurse_all = True
continue
wildcard_prefixes.add(pathlib.PurePosixPath(*literal_parts))
continue
parent_parts = parts[:-1]
for idx in range(1, len(parent_parts) + 1):
exact_dirs.add(pathlib.PurePosixPath(*parent_parts[:idx]))
return _NegatedDockerignoreHints(
exact_dirs=frozenset(exact_dirs),
wildcard_prefixes=frozenset(wildcard_prefixes),
recurse_all=recurse_all,
)
+10 -3
View File
@@ -26,8 +26,15 @@ class LogData(TypedDict):
params: dict[str, Any]
def get_anonymized_params(kwargs: dict[str, Any]) -> dict[str, bool]:
params = {}
def get_anonymized_params(
kwargs: dict[str, Any], *, cli_command: str
) -> dict[str, bool | str]:
params: dict[str, bool | str] = {}
if cli_command == "deploy" and (
analytics_source := os.getenv("LANGGRAPH_CLI_ANALYTICS_SOURCE")
):
params["source"] = analytics_source
# anonymize params with values
if config := kwargs.get("config"):
@@ -88,7 +95,7 @@ def log_command(func):
"python_version": platform.python_version(),
"cli_version": __version__,
"cli_command": func.__name__,
"params": get_anonymized_params(kwargs),
"params": get_anonymized_params(kwargs, cli_command=func.__name__),
}
background_thread = threading.Thread(target=log_data, args=(data,))
+1 -24
View File
@@ -9,35 +9,12 @@ from contextlib import contextmanager
import click
import pathspec
from langgraph_cli._ignore import _build_ignore_spec
from langgraph_cli.config import Config, _assemble_local_deps
_WARN_SIZE = 50 * 1024 * 1024 # 50 MB
_MAX_SIZE = 200 * 1024 * 1024 # 200 MB
_ALWAYS_EXCLUDE = [
"__pycache__/",
".git/",
".venv/",
"venv/",
"node_modules/",
".tox/",
".mypy_cache/",
]
def _build_ignore_spec(directory: pathlib.Path) -> pathspec.PathSpec:
"""Build a PathSpec combining built-in exclusions with .dockerignore and .gitignore.
Always excludes common non-source directories (_ALWAYS_EXCLUDE). On top of
that, patterns from .dockerignore and .gitignore (if present) are merged in.
"""
lines: list[str] = list(_ALWAYS_EXCLUDE)
for name in (".dockerignore", ".gitignore"):
ignore_file = directory / name
if ignore_file.is_file():
lines.extend(ignore_file.read_text(encoding="utf-8").splitlines())
return pathspec.PathSpec.from_lines("gitwildmatch", lines)
def _tar_filter(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
"""Strip symlinks, hardlinks, and traversal paths from archive."""
+50 -11
View File
@@ -10,7 +10,13 @@ except ModuleNotFoundError: # pragma: no cover - exercised on Python 3.10.
import tomli as tomllib
import click
import pathspec
from langgraph_cli._ignore import (
_build_dockerignore_negation_hints,
_build_ignore_spec,
_is_always_excluded,
)
from langgraph_cli.schemas import Config
@@ -440,16 +446,32 @@ def _container_root_for_uv_lock_package(
def _uv_lock_package_copy_items(
package: UvLockPackage, plan: UvLockPlan
package: UvLockPackage,
plan: UvLockPlan,
ignore_spec: pathspec.PathSpec,
) -> tuple[tuple[pathlib.PurePosixPath, pathlib.PurePosixPath], ...]:
# Skip entries that .dockerignore / built-in exclusions would strip from
# the build context. Emitting `ADD <path>` for a file that Docker has
# filtered out causes the build to fail with
# "failed to compute cache key: <path> not found".
if package.root != plan.project_root:
relative_root = pathlib.PurePosixPath(
*package.root.relative_to(plan.project_root).parts
)
if _is_always_excluded(relative_root, is_dir=True) or ignore_spec.match_file(
f"{relative_root.as_posix()}/"
):
raise click.UsageError(
f"Workspace member '{package.name}' at {relative_root} is "
"excluded from the Docker build context, but uv.lock requires "
"it to be copied into the build context. Remove the matching "
"pattern or drop the member from [tool.uv.workspace].members."
)
return ((relative_root, plan.container_roots[package.root]),)
root_container = plan.container_roots[package.root]
workspace_member_roots = plan.all_workspace_roots - {plan.project_root}
negated_dockerignore_hints = _build_dockerignore_negation_hints(plan.project_root)
def iter_entries(
current_dir: pathlib.Path,
@@ -461,18 +483,32 @@ def _uv_lock_package_copy_items(
# and excluded entirely otherwise.
continue
descendant_member_roots = [
ws_root
for ws_root in workspace_member_roots
if child in ws_root.parents
]
if child.is_dir() and descendant_member_roots:
entries.extend(iter_entries(child))
continue
relative_child = pathlib.PurePosixPath(
*child.relative_to(plan.project_root).parts
)
is_dir = child.is_dir()
if _is_always_excluded(relative_child, is_dir=is_dir):
continue
ignored = ignore_spec.match_file(
f"{relative_child.as_posix()}/" if is_dir else relative_child.as_posix()
)
is_workspace_parent = is_dir and any(
child in ws_root.parents for ws_root in workspace_member_roots
)
if is_workspace_parent:
entries.extend(iter_entries(child))
continue
if (
is_dir
and ignored
and negated_dockerignore_hints.requires_dir_walk(relative_child)
):
entries.extend(iter_entries(child))
continue
if ignored:
continue
entries.append(
(relative_child, root_container.joinpath(*relative_child.parts))
)
@@ -956,10 +992,13 @@ def python_config_to_docker_uv_lock(
docker_plan.add_raw("# -- End of uv.lock dependencies install --")
docker_plan.add_blank()
ignore_spec = _build_ignore_spec(plan.project_root, include_gitignore=False)
for package in plan.install_order:
package_label = package.root.relative_to(plan.project_root).as_posix() or "."
docker_plan.add_raw(f"# -- Adding workspace package {package_label} --")
for source, destination in _uv_lock_package_copy_items(package, plan):
for source, destination in _uv_lock_package_copy_items(
package, plan, ignore_spec
):
docker_plan.add_raw(copy_from_project_root(source, destination.as_posix()))
docker_plan.add_instruction(
"WORKDIR", plan.container_roots[package.root].as_posix()
+2 -2
View File
@@ -23,13 +23,13 @@ dependencies = [
path = "langgraph_cli/__init__.py"
[project.optional-dependencies]
inmem = [
"langgraph-api>=0.5.35,<0.8.0 ; python_version >= '3.11'",
"langgraph-api>=0.5.35,<0.9.0 ; python_version >= '3.11'",
"langgraph-runtime-inmem>=0.7 ; python_version >= '3.11'",
]
[project.urls]
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/cli"
Twitter = "https://x.com/LangChain"
Twitter = "https://x.com/langchain_oss"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
@@ -99,6 +99,13 @@ class TestBuildIgnoreSpec:
assert spec.match_file("app.log")
assert spec.match_file("mod.pyc")
def test_can_skip_gitignore(self, tmp_path):
(tmp_path / ".dockerignore").write_text("*.log\n")
(tmp_path / ".gitignore").write_text("*.pyc\n")
spec = _build_ignore_spec(tmp_path, include_gitignore=False)
assert spec.match_file("app.log")
assert not spec.match_file("mod.pyc")
def test_no_ignore_files_only_builtins(self, tmp_path):
spec = _build_ignore_spec(tmp_path)
assert spec.match_file("__pycache__/")
+359
View File
@@ -4,6 +4,7 @@ import os
import pathlib
import tempfile
import textwrap
from unittest.mock import patch
import click
import pytest
@@ -1855,6 +1856,364 @@ def test_config_to_docker_uv_lock_supports_single_uv_project_root():
assert additional_contexts == {}
def test_config_to_docker_uv_lock_skips_dockerignore_entries():
"""Entries filtered by .dockerignore / built-in excludes must not appear
as ADD lines. Docker fails to compute the cache key for paths that the
build context has stripped."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root = tmpdir_path / "single"
project_root.mkdir()
(project_root / "uv.lock").write_text("# uv lock file\n")
(project_root / "pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
name = "single-app"
version = "0.1.0"
dependencies = ["httpx>=0.28"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
"""
).strip()
+ "\n"
)
(project_root / "langgraph.json").write_text("{}\n")
(project_root / "src").mkdir()
(project_root / "src" / "agent.py").write_text("graph = object()\n")
(project_root / "README.md").write_text("# hi\n")
# Built-in exclusions — must never appear as ADD lines.
(project_root / ".git").mkdir()
(project_root / ".git" / "HEAD").write_text("ref: refs/heads/main\n")
(project_root / ".venv").mkdir()
(project_root / ".venv" / "pyvenv.cfg").write_text("home = /usr\n")
(project_root / "__pycache__").mkdir()
(project_root / "__pycache__" / "x.cpython-311.pyc").write_bytes(b"\x00")
# .dockerignore excludes .gitignore and a custom path.
(project_root / ".dockerignore").write_text(".gitignore\nsecrets.env\n")
(project_root / ".gitignore").write_text("*.pyc\n")
(project_root / "secrets.env").write_text("TOKEN=abc\n")
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
docker, _ = config_to_docker(
project_root / "langgraph.json",
config,
base_image="langchain/langgraph-api:0.2.47",
)
for excluded in (
"ADD .git ",
"ADD .gitignore ",
"ADD .venv ",
"ADD __pycache__ ",
"ADD secrets.env ",
):
assert excluded not in docker, (
f"{excluded!r} should be filtered out of Dockerfile:\n{docker}"
)
# The .dockerignore itself is still part of the context and should be
# ADDed (Docker needs it at build time, and archive.py includes it).
assert "ADD .dockerignore /deps/workspace/.dockerignore" in docker
assert "ADD src /deps/workspace/src" in docker
assert "ADD README.md /deps/workspace/README.md" in docker
def test_config_to_docker_uv_lock_does_not_apply_gitignore():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root = tmpdir_path / "single"
project_root.mkdir()
(project_root / "uv.lock").write_text("# uv lock file\n")
(project_root / "pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
name = "single-app"
version = "0.1.0"
dependencies = ["httpx>=0.28"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
"""
).strip()
+ "\n"
)
(project_root / "langgraph.json").write_text("{}\n")
(project_root / "src").mkdir()
(project_root / "src" / "agent.py").write_text("graph = object()\n")
(project_root / "README.md").write_text("# hi\n")
(project_root / ".gitignore").write_text("README.md\n")
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
docker, _ = config_to_docker(
project_root / "langgraph.json",
config,
base_image="langchain/langgraph-api:0.2.47",
)
assert "ADD README.md /deps/workspace/README.md" in docker
def test_config_to_docker_uv_lock_skips_dockerignore_entries_in_workspace():
"""Multi-member workspace: ignore patterns must filter root-level entries
AND entries encountered while recursing into directories that contain
workspace members (the `descendant_member_roots` branch)."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root, config_path = _write_uv_lock_workspace(
tmpdir_path,
agent_dependencies=["workspace-root", "shared", "httpx>=0.28"],
root_sources="[tool.uv.sources]\nshared = { workspace = true }\nworkspace-root = { workspace = true }",
agent_sources="[tool.uv.sources]\nshared = { workspace = true }\nworkspace-root = { workspace = true }",
)
root_src = project_root / "src" / "workspace_root"
root_src.mkdir(parents=True)
(root_src / "__init__.py").write_text("__all__ = []\n")
(project_root / "README.md").write_text("workspace root package\n")
# A non-member sibling of the `apps/agent` member that should be
# filtered out via .dockerignore. This exercises the recursion into
# `apps/` where `apps/agent` is kept (it's a member) but its sibling is
# filtered.
(project_root / "apps" / "scratch.txt").write_text("scratch\n")
# A root-level path that .dockerignore excludes.
(project_root / "secrets.env").write_text("TOKEN=abc\n")
(project_root / ".dockerignore").write_text("secrets.env\napps/scratch.txt\n")
config = validate_config(
{
"python_version": "3.11",
"graphs": {
"agent": "../../apps/agent/src/agent/graph.py:graph",
},
"source": {"kind": "uv", "root": "../..", "package": "agent"},
}
)
docker, _ = config_to_docker(
config_path, config, base_image="langchain/langgraph-api:0.2.47"
)
assert "COPY --from=uv-workspace-root src /deps/workspace/src" in docker
assert (
"COPY --from=uv-workspace-root README.md /deps/workspace/README.md"
in docker
)
assert (
"COPY --from=uv-workspace-root .dockerignore /deps/workspace/.dockerignore"
in docker
)
assert "secrets.env" not in docker
assert "apps/scratch.txt" not in docker
# Workspace members themselves are still copied via their own per-member
# COPY line — the sibling filter must not disturb this.
assert (
"COPY --from=uv-workspace-root apps/agent /deps/workspace/apps/agent"
in docker
)
def test_config_to_docker_uv_lock_preserves_negated_dockerignore_descendants():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root = tmpdir_path / "single"
project_root.mkdir()
(project_root / "uv.lock").write_text("# uv lock file\n")
(project_root / "pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
name = "single-app"
version = "0.1.0"
dependencies = ["httpx>=0.28"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
"""
).strip()
+ "\n"
)
(project_root / "langgraph.json").write_text("{}\n")
(project_root / "src").mkdir()
(project_root / "src" / "agent.py").write_text("graph = object()\n")
(project_root / "assets").mkdir()
(project_root / "assets" / "keep.txt").write_text("keep\n")
(project_root / "assets" / "drop.txt").write_text("drop\n")
(project_root / ".dockerignore").write_text("assets/\n!assets/keep.txt\n")
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
docker, _ = config_to_docker(
project_root / "langgraph.json",
config,
base_image="langchain/langgraph-api:0.2.47",
)
assert "ADD assets /deps/workspace/assets" not in docker
assert "ADD assets/keep.txt /deps/workspace/assets/keep.txt" in docker
assert "assets/drop.txt" not in docker
def test_config_to_docker_uv_lock_prunes_unrelated_ignored_subtrees():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root = tmpdir_path / "single"
project_root.mkdir()
(project_root / "uv.lock").write_text("# uv lock file\n")
(project_root / "pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
name = "single-app"
version = "0.1.0"
dependencies = ["httpx>=0.28"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
"""
).strip()
+ "\n"
)
(project_root / "langgraph.json").write_text("{}\n")
(project_root / "src").mkdir()
(project_root / "src" / "agent.py").write_text("graph = object()\n")
(project_root / "assets").mkdir()
(project_root / "assets" / "keep.txt").write_text("keep\n")
(project_root / "vendor").mkdir()
(project_root / "vendor" / "huge.txt").write_text("large\n")
(project_root / ".dockerignore").write_text(
"vendor/\nassets/\n!assets/keep.txt\n"
)
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
original_iterdir = pathlib.Path.iterdir
def guarded_iterdir(self):
if self == project_root / "vendor":
raise AssertionError("should not walk unrelated ignored subtree")
return original_iterdir(self)
with patch.object(
pathlib.Path, "iterdir", autospec=True, side_effect=guarded_iterdir
):
docker, _ = config_to_docker(
project_root / "langgraph.json",
config,
base_image="langchain/langgraph-api:0.2.47",
)
assert "ADD assets/keep.txt /deps/workspace/assets/keep.txt" in docker
assert "vendor/huge.txt" not in docker
def test_config_to_docker_uv_lock_never_reincludes_always_excluded_subtrees():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root = tmpdir_path / "single"
project_root.mkdir()
(project_root / "uv.lock").write_text("# uv lock file\n")
(project_root / "pyproject.toml").write_text(
textwrap.dedent(
"""
[project]
name = "single-app"
version = "0.1.0"
dependencies = ["httpx>=0.28"]
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
"""
).strip()
+ "\n"
)
(project_root / "langgraph.json").write_text("{}\n")
(project_root / "src").mkdir()
(project_root / "src" / "agent.py").write_text("graph = object()\n")
(project_root / ".venv" / "pkg").mkdir(parents=True)
(project_root / ".venv" / "pkg" / "keep.txt").write_text("keep\n")
(project_root / "node_modules" / "pkg").mkdir(parents=True)
(project_root / "node_modules" / "pkg" / "package.json").write_text("{}\n")
(project_root / ".dockerignore").write_text(
"!.venv/pkg/keep.txt\n!node_modules/pkg/package.json\n"
)
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "./src/agent.py:graph"},
"source": {"kind": "uv"},
}
)
docker, _ = config_to_docker(
project_root / "langgraph.json",
config,
base_image="langchain/langgraph-api:0.2.47",
)
assert ".venv/pkg/keep.txt" not in docker
assert "node_modules/pkg/package.json" not in docker
assert "ADD src /deps/workspace/src" in docker
def test_config_to_docker_uv_lock_rejects_ignored_workspace_member():
"""A workspace member matched by .dockerignore cannot be copied into the
build context uv.lock requires it, so fail loudly with a clear message."""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
project_root, config_path = _write_uv_lock_workspace(
tmpdir_path,
agent_sources="[tool.uv.sources]\nshared = { workspace = true }",
)
(project_root / ".dockerignore").write_text("libs/shared\n")
config = validate_config(
{
"python_version": "3.11",
"graphs": {"agent": "../../apps/agent/src/agent/graph.py:graph"},
"source": {"kind": "uv", "root": "../..", "package": "agent"},
"auth": {"path": "../../libs/shared/src/shared/auth.py:create_auth"},
}
)
with pytest.raises(
click.UsageError, match=r"Workspace member 'shared' at libs/shared"
):
config_to_docker(
config_path, config, base_image="langchain/langgraph-api:0.2.47"
)
def test_config_to_docker_uv_lock_rejects_invalid_source_package_type():
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = pathlib.Path(tmpdir)
+6 -6
View File
@@ -215,7 +215,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.27"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -227,9 +227,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/13/5c/56d19a252bbb26247b7a7cd20821d48804d7ca03212fec709cd8db7c2516/langchain_core-1.2.27.tar.gz", hash = "sha256:c18372e4c4c1454d49bf23a2e484431e71bd39b64173a0f621f0fc283d7183a4", size = 844935, upload-time = "2026-04-07T14:56:32.364Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/c3/6e0865bc130c448270eb9511b47863a3f9145cdb519b19f6e4758fa63d6f/langchain_core-1.2.27-py3-none-any.whl", hash = "sha256:9ecd6b0393b969fe88f6b9b309367134080ab095946d79e6937dd3911aa42bd5", size = 508315, upload-time = "2026-04-07T14:56:30.93Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
@@ -290,7 +290,7 @@ wheels = [
[[package]]
name = "langsmith"
version = "0.7.26"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -303,9 +303,9 @@ dependencies = [
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/86/6de4f6f0451a9658f26f633e0bb090552a4dafd7df3f1ae7f0d40558e67e/langsmith-0.7.26.tar.gz", hash = "sha256:a3e06f3d689ce7195717aa6b8f91082319819ec7ea9b9a62cdcd3d9dc25bfc7b", size = 1146118, upload-time = "2026-04-06T15:01:03.336Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/8e/7eb7d65ce62e98e74b9f18f193ea7ac3996d4fbd71fffcc67d0f7ba3103e/langsmith-0.7.26-py3-none-any.whl", hash = "sha256:fe5c877972cea450c1c48251c8fae0f18543c8d19dfdb9ff9a9c4263763dde4e", size = 360160, upload-time = "2026-04-06T15:01:01.516Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
+6 -6
View File
@@ -191,7 +191,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.2.27"
version = "1.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -203,9 +203,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/13/5c/56d19a252bbb26247b7a7cd20821d48804d7ca03212fec709cd8db7c2516/langchain_core-1.2.27.tar.gz", hash = "sha256:c18372e4c4c1454d49bf23a2e484431e71bd39b64173a0f621f0fc283d7183a4", size = 844935, upload-time = "2026-04-07T14:56:32.364Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/317a1a3ac1df33a64adb3670bf88bbe3b3d5baa274db6863a979db472897/langchain_core-1.2.28.tar.gz", hash = "sha256:271a3d8bd618f795fdeba112b0753980457fc90537c46a0c11998516a74dc2cb", size = 846119, upload-time = "2026-04-08T18:19:34.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/c3/6e0865bc130c448270eb9511b47863a3f9145cdb519b19f6e4758fa63d6f/langchain_core-1.2.27-py3-none-any.whl", hash = "sha256:9ecd6b0393b969fe88f6b9b309367134080ab095946d79e6937dd3911aa42bd5", size = 508315, upload-time = "2026-04-07T14:56:30.93Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/32f785f077c7e898da97064f113c73fbd9ad55d1e2169cf3a391b183dedb/langchain_core-1.2.28-py3-none-any.whl", hash = "sha256:80764232581eaf8057bcefa71dbf8adc1f6a28d257ebd8b95ba9b8b452e8c6ac", size = 508727, upload-time = "2026-04-08T18:19:32.823Z" },
]
[[package]]
@@ -266,7 +266,7 @@ wheels = [
[[package]]
name = "langsmith"
version = "0.7.26"
version = "0.7.31"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -279,9 +279,9 @@ dependencies = [
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/76/86/6de4f6f0451a9658f26f633e0bb090552a4dafd7df3f1ae7f0d40558e67e/langsmith-0.7.26.tar.gz", hash = "sha256:a3e06f3d689ce7195717aa6b8f91082319819ec7ea9b9a62cdcd3d9dc25bfc7b", size = 1146118, upload-time = "2026-04-06T15:01:03.336Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e6/11/696019490992db5c87774dc20515529ef42a01e1d770fb754ed6d9b12fb0/langsmith-0.7.31.tar.gz", hash = "sha256:331ee4f7c26bb5be4022b9859b7d7b122cbf8c9d01d9f530114c1914b0349ffb", size = 1178480, upload-time = "2026-04-14T17:55:41.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/8e/7eb7d65ce62e98e74b9f18f193ea7ac3996d4fbd71fffcc67d0f7ba3103e/langsmith-0.7.26-py3-none-any.whl", hash = "sha256:fe5c877972cea450c1c48251c8fae0f18543c8d19dfdb9ff9a9c4263763dde4e", size = 360160, upload-time = "2026-04-06T15:01:01.516Z" },
{ url = "https://files.pythonhosted.org/packages/1d/a1/a013cf458c301cda86a213dd153ce0a01c93f1ab5833f951e6a44c9763ce/langsmith-0.7.31-py3-none-any.whl", hash = "sha256:0291d49203f6e80dda011af1afda61eb0595a4d697adb684590a8805e1d61fb6", size = 373276, upload-time = "2026-04-14T17:55:39.677Z" },
]
[[package]]
+523 -440
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -18,7 +18,7 @@
<a href="https://pypi.org/project/langgraph/" target="_blank"><img src="https://img.shields.io/pypi/v/langgraph.svg?label=%20" alt="Version"></a>
<a href="https://github.com/langchain-ai/langgraph/issues" target="_blank"><img src="https://img.shields.io/github/issues-raw/langchain-ai/langgraph" alt="Open Issues"></a>
<a href="https://docs.langchain.com/oss/python/langgraph/overview" target="_blank"><img src="https://img.shields.io/badge/docs-latest-blue" alt="Docs"></a>
<a href="https://x.com/langchain" target="_blank"><img src="https://img.shields.io/twitter/url/https/twitter.com/langchain.svg?style=social&label=Follow%20%40LangChain" alt="Twitter / X"></a>
<a href="https://x.com/langchain_oss" target="_blank"><img src="https://img.shields.io/twitter/url/https/twitter.com/langchain_oss.svg?style=social&label=Follow%20%40LangChain" alt="Twitter / X"></a>
</div>
<br>
+48 -12
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from collections import ChainMap
from collections.abc import Mapping, Sequence
from collections.abc import Sequence
from os import getenv
from typing import Any, cast
@@ -217,14 +217,16 @@ def get_callback_manager_for_config(
callbacks.add_tags(all_tags)
if metadata := config.get("metadata"):
callbacks.add_metadata(metadata)
return callbacks
manager = callbacks
else:
# otherwise create a new manager
return CallbackManager.configure(
manager = CallbackManager.configure(
inheritable_callbacks=config.get("callbacks"),
inheritable_tags=all_tags,
inheritable_metadata=config.get("metadata"),
langsmith_inheritable_metadata=_get_tracing_metadata_defaults(config),
)
return manager
def get_async_callback_manager_for_config(
@@ -255,14 +257,16 @@ def get_async_callback_manager_for_config(
callbacks.add_tags(all_tags)
if metadata := config.get("metadata"):
callbacks.add_metadata(metadata)
return callbacks
manager = callbacks
else:
# otherwise create a new manager
return AsyncCallbackManager.configure(
manager = AsyncCallbackManager.configure(
inheritable_callbacks=config.get("callbacks"),
inheritable_tags=all_tags,
inheritable_metadata=config.get("metadata"),
langsmith_inheritable_metadata=_get_tracing_metadata_defaults(config),
)
return manager
def _is_not_empty(value: Any) -> bool:
@@ -308,22 +312,54 @@ def ensure_config(*configs: RunnableConfig | None) -> RunnableConfig:
for k, v in config.items():
if _is_not_empty(v) and k not in CONFIG_KEYS:
empty[CONF][k] = v
_empty_metadata = empty["metadata"]
for key, value in empty[CONF].items():
if _exclude_as_metadata(key, value, _empty_metadata):
continue
_empty_metadata[key] = value
configurable = empty.get("configurable")
metadata = empty.get("metadata")
if configurable and metadata is not None:
for key in _PROPAGATE_TO_METADATA:
if key in metadata:
continue
value = configurable.get(key)
if value:
metadata[key] = value
return empty
_OMIT = ("key", "token", "secret", "password", "auth")
def _exclude_as_metadata(key: str, value: Any, metadata: Mapping[str, Any]) -> bool:
def _exclude_as_metadata(key: str, value: Any) -> bool:
key_lower = key.casefold()
return (
key.startswith("__")
or not isinstance(value, (str, int, float, bool))
or key in metadata
or any(substr in key_lower for substr in _OMIT)
)
def _get_tracing_metadata_defaults(
config: RunnableConfig,
) -> dict[str, Any] | None:
"""Get tracer-only metadata defaults from configurable values."""
configurable = config.get("configurable")
if not configurable:
return None
metadata: dict[str, Any] = {}
for key, value in configurable.items():
if _exclude_as_metadata(key, value):
continue
metadata[key] = value
return metadata or None
_PROPAGATE_TO_METADATA = frozenset(
(
"thread_id",
"checkpoint_id",
"checkpoint_ns",
"task_id",
"run_id",
"assistant_id",
"graph_id",
)
)
@@ -12,6 +12,9 @@ RESUME = sys.intern("__resume__")
# for values passed to resume a node after an interrupt
ERROR = sys.intern("__error__")
# for errors raised by nodes
ERROR_SOURCE_NODE = sys.intern("__error_source_node__")
# failed source node name for node-level error handlers
# value format in pending writes: `(task_id, ERROR_SOURCE_NODE, node_name: str)`
NO_WRITES = sys.intern("__no_writes__")
# marker to signal node didn't write anything
TASKS = sys.intern("__pregel_tasks")
@@ -56,6 +59,8 @@ CONFIG_KEY_CHECKPOINT_NS = sys.intern("checkpoint_ns")
# holds the current checkpoint_ns, "" for root graph
CONFIG_KEY_NODE_FINISHED = sys.intern("__pregel_node_finished")
# holds a callback to be called when a node is finished
CONFIG_KEY_TIMED_ATTEMPT_OBSERVER = sys.intern("__pregel_timed_attempt_observer")
# holds a callback to be called when an idle-timed node attempt starts or finishes
CONFIG_KEY_SCRATCHPAD = sys.intern("__pregel_scratchpad")
# holds a mutable dict for temporary storage scoped to the current task
CONFIG_KEY_RUNNER_SUBMIT = sys.intern("__pregel_runner_submit")
@@ -66,6 +71,13 @@ 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_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.
CONFIG_KEY_NODE_ERROR = sys.intern("__pregel_node_error")
# holds a `NodeError` (failed source node + exception) for the current
# node-level error handler invocation, injected when handler signature
# requests `error: NodeError`
# --- Other constants ---
PUSH = sys.intern("__pregel_push")
@@ -93,6 +105,7 @@ RESERVED = {
INTERRUPT,
RESUME,
ERROR,
ERROR_SOURCE_NODE,
NO_WRITES,
# reserved config.configurable keys
CONFIG_KEY_SEND,
@@ -106,7 +119,9 @@ RESERVED = {
CONFIG_KEY_CHECKPOINT_MAP,
CONFIG_KEY_CHECKPOINT_ID,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_TIMED_ATTEMPT_OBSERVER,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_STREAM_MESSAGES_V2,
# other constants
PUSH,
PULL,
@@ -51,9 +51,11 @@ from langgraph._internal._config import (
)
from langgraph._internal._constants import (
CONF,
CONFIG_KEY_NODE_ERROR,
CONFIG_KEY_RUNTIME,
)
from langgraph._internal._typing import MISSING
from langgraph.errors import NodeError
from langgraph.types import StreamWriter
try:
@@ -117,6 +119,19 @@ def set_config_context(
ctx.run(_unset_config_context, config_token, run)
def create_task_in_config_context(
coro_factory: Callable[[], Coroutine[Any, Any, Any]], config: RunnableConfig
) -> asyncio.Task[Any]:
"""Create an asyncio.Task that inherits `config` as the child runnable context.
`asyncio.create_task` snapshots the current contextvars onto the new task,
so calling `create_task` while the config context is set ensures the task
sees `config` via `var_child_runnable_config` and any tracing parent.
"""
with set_config_context(config) as context:
return context.run(lambda: asyncio.create_task(coro_factory()))
# Before Python 3.11 native StrEnum is not available
class StrEnum(str, enum.Enum):
"""A string enum."""
@@ -181,6 +196,15 @@ KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
"N/A",
inspect.Parameter.empty,
),
(
"error",
(NodeError, "NodeError"),
# we never hit this block, we read directly from configurable
"N/A",
# default to None so non-handler nodes that happen to type a parameter
# `error: NodeError` don't blow up; handlers always receive a NodeError.
None,
),
)
"""List of kwargs that can be passed to functions, and their corresponding
config keys, default values and type annotations.
@@ -354,6 +378,8 @@ class RunnableCallable(Runnable):
kw_value: Any = MISSING
if kw == "config":
kw_value = config
elif kw == "error":
kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
elif runtime:
if kw == "runtime":
kw_value = runtime
@@ -426,6 +452,8 @@ class RunnableCallable(Runnable):
kw_value: Any = MISSING
if kw == "config":
kw_value = config
elif kw == "error":
kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
elif runtime:
if kw == "runtime":
kw_value = runtime
@@ -0,0 +1,25 @@
from __future__ import annotations
from datetime import timedelta
from typing import Literal
from langgraph.types import TimeoutPolicy
_SYNC_TIMEOUT_PREFIX = (
"Node timeouts are only supported for async nodes because sync Python "
"execution cannot be safely cancelled in-process."
)
def coerce_timeout_policy(
value: float | timedelta | TimeoutPolicy | None,
) -> TimeoutPolicy | None:
"""Normalize a timeout value to positive-second policy fields."""
return TimeoutPolicy.coerce(value)
def sync_timeout_unsupported(
name: str, *, kind: Literal["Node", "Task"] = "Node"
) -> ValueError:
"""Build the canonical error for using `timeout` with a sync target."""
return ValueError(f"{_SYNC_TIMEOUT_PREFIX} {kind} {name!r} is sync.")
+394
View File
@@ -0,0 +1,394 @@
"""Graph lifecycle callback interfaces and event payloads.
This module defines the public callback surface for observing LangGraph-specific
lifecycle transitions such as interrupt and resume.
"""
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any, Literal, TypeAlias, TypeVar
from uuid import UUID
from langchain_core.callbacks import BaseCallbackHandler, BaseCallbackManager
from langchain_core.callbacks.manager import ahandle_event, handle_event
from langchain_core.runnables import RunnableConfig
from langgraph.types import Interrupt
__all__ = (
"GraphCallbackHandler",
"GraphInterruptEvent",
"GraphLifecycleEvent",
"GraphLifecycleStatus",
"GraphResumeEvent",
"get_async_graph_callback_manager_for_config",
"get_sync_graph_callback_manager_for_config",
)
GraphLifecycleStatus: TypeAlias = Literal[
"input",
"pending",
"done",
"interrupt_before",
"interrupt_after",
"out_of_steps",
]
"""Allowed lifecycle statuses reported in graph lifecycle callback events."""
@dataclass(frozen=True)
class GraphInterruptEvent:
"""Graph lifecycle event emitted when execution pauses for interrupts."""
run_id: UUID | None
"""Run id for the current graph execution, if available."""
status: GraphLifecycleStatus
"""Loop status when the interrupt was captured."""
checkpoint_id: str
"""Checkpoint id associated with the interrupted execution."""
checkpoint_ns: tuple[str, ...]
"""Checkpoint namespace path for the current graph or subgraph."""
interrupts: tuple[Interrupt, ...]
"""Interrupt payloads that caused the graph to pause."""
@dataclass(frozen=True)
class GraphResumeEvent:
"""Graph lifecycle event emitted when execution resumes from a checkpoint."""
run_id: UUID | None
"""Run id for the current graph execution, if available."""
status: GraphLifecycleStatus
"""Loop status when the resume was captured."""
checkpoint_id: str
"""Checkpoint id the graph resumed from."""
checkpoint_ns: tuple[str, ...]
"""Checkpoint namespace path for the current graph or subgraph."""
GraphLifecycleEvent: TypeAlias = GraphInterruptEvent | GraphResumeEvent
"""Union of all public graph lifecycle callback event payloads.
Use this alias when a callback or helper can receive either interrupt or resume
lifecycle events.
"""
class GraphCallbackHandler(BaseCallbackHandler):
"""Base class for graph-level lifecycle callbacks.
Subclass this handler to observe graph lifecycle transitions that are
specific to LangGraph execution, rather than generic LangChain runnable
callbacks.
Instances can be passed through `config["callbacks"]` when invoking a
graph. Only handlers that inherit from `GraphCallbackHandler` receive these
lifecycle events.
"""
def on_interrupt(self, event: GraphInterruptEvent) -> Any:
"""Run when graph execution pauses due to one or more interrupts.
Args:
event: Interrupt lifecycle event payload.
"""
def on_resume(self, event: GraphResumeEvent) -> Any:
"""Run when graph execution resumes from a persisted checkpoint.
Args:
event: Resume lifecycle event payload.
"""
_MISSING = object()
def _filter_graph_handlers(
handlers: list[BaseCallbackHandler],
) -> list[GraphCallbackHandler]:
return [h for h in handlers if isinstance(h, GraphCallbackHandler)]
def _init_base_manager(
manager: BaseCallbackManager,
handlers: Sequence[GraphCallbackHandler] | None,
inheritable_handlers: Sequence[GraphCallbackHandler] | None,
parent_run_id: UUID | None,
*,
tags: list[str] | None,
inheritable_tags: list[str] | None,
metadata: dict[str, Any] | None,
inheritable_metadata: dict[str, Any] | None,
run_id: UUID | None,
) -> None:
base_handlers: list[BaseCallbackHandler] = []
base_inheritable_handlers: list[BaseCallbackHandler] = []
if handlers is not None:
base_handlers.extend(handlers)
if inheritable_handlers is not None:
base_inheritable_handlers.extend(inheritable_handlers)
BaseCallbackManager.__init__(
manager,
handlers=base_handlers,
inheritable_handlers=base_inheritable_handlers,
parent_run_id=parent_run_id,
tags=tags,
inheritable_tags=inheritable_tags,
metadata=metadata,
inheritable_metadata=inheritable_metadata,
)
manager.run_id = run_id # type: ignore[attr-defined]
def _configure_graph_callbacks(
cls: type[_GraphManagerT],
callbacks: object | None,
*,
run_id: UUID | None,
) -> _GraphManagerT:
if callbacks is None:
return cls(run_id=run_id)
if isinstance(callbacks, cls):
return callbacks.copy(run_id=run_id)
if isinstance(callbacks, (_GraphCallbackManager, _AsyncGraphCallbackManager)):
# Cross-type: extract handlers into the requested cls.
return cls(
handlers=_filter_graph_handlers(callbacks.handlers),
inheritable_handlers=_filter_graph_handlers(callbacks.inheritable_handlers),
parent_run_id=callbacks.parent_run_id,
tags=callbacks.tags.copy(),
inheritable_tags=callbacks.inheritable_tags.copy(),
metadata=callbacks.metadata.copy(),
inheritable_metadata=callbacks.inheritable_metadata.copy(),
run_id=run_id,
)
if isinstance(callbacks, BaseCallbackManager):
return cls(
handlers=_filter_graph_handlers(callbacks.handlers),
inheritable_handlers=_filter_graph_handlers(callbacks.inheritable_handlers),
parent_run_id=callbacks.parent_run_id,
tags=callbacks.tags.copy(),
inheritable_tags=callbacks.inheritable_tags.copy(),
metadata=callbacks.metadata.copy(),
inheritable_metadata=callbacks.inheritable_metadata.copy(),
run_id=run_id,
)
if isinstance(callbacks, GraphCallbackHandler):
return cls((callbacks,), run_id=run_id)
if isinstance(callbacks, (str, bytes)) or not isinstance(callbacks, Sequence):
raise TypeError("callbacks must be a handler, sequence, or manager")
return cls(_filter_graph_handlers(list(callbacks)), run_id=run_id)
def _copy_graph_manager(
manager: _GraphCallbackManager | _AsyncGraphCallbackManager,
cls: type[_GraphManagerT],
run_id: UUID | None | object,
) -> _GraphManagerT:
resolved_run_id: UUID | None
if run_id is _MISSING:
resolved_run_id = manager.run_id
else:
if run_id is not None and not isinstance(run_id, UUID):
raise TypeError("run_id must be a UUID or None")
resolved_run_id = run_id
return cls(
handlers=_filter_graph_handlers(manager.handlers),
inheritable_handlers=_filter_graph_handlers(manager.inheritable_handlers),
parent_run_id=manager.parent_run_id,
tags=manager.tags.copy(),
inheritable_tags=manager.inheritable_tags.copy(),
metadata=manager.metadata.copy(),
inheritable_metadata=manager.inheritable_metadata.copy(),
run_id=resolved_run_id,
)
class _GraphCallbackManager(BaseCallbackManager):
"""Sync dispatcher for graph lifecycle events."""
run_id: UUID | None
def __init__(
self,
handlers: Sequence[GraphCallbackHandler] | None = None,
inheritable_handlers: Sequence[GraphCallbackHandler] | None = None,
parent_run_id: UUID | None = None,
*,
tags: list[str] | None = None,
inheritable_tags: list[str] | None = None,
metadata: dict[str, Any] | None = None,
inheritable_metadata: dict[str, Any] | None = None,
run_id: UUID | None = None,
) -> None:
_init_base_manager(
self,
handlers,
inheritable_handlers,
parent_run_id,
tags=tags,
inheritable_tags=inheritable_tags,
metadata=metadata,
inheritable_metadata=inheritable_metadata,
run_id=run_id,
)
def copy(
self,
*,
run_id: UUID | None | object = _MISSING,
) -> _GraphCallbackManager:
return _copy_graph_manager(self, _GraphCallbackManager, run_id)
@classmethod
def configure(
cls,
callbacks: object | None = None,
*,
run_id: UUID | None = None,
) -> _GraphCallbackManager:
return _configure_graph_callbacks(cls, callbacks, run_id=run_id)
def on_interrupt(self, event: GraphInterruptEvent) -> None:
handle_event(
self.handlers,
"on_interrupt",
None,
event,
)
def on_resume(self, event: GraphResumeEvent) -> None:
handle_event(
self.handlers,
"on_resume",
None,
event,
)
class _AsyncGraphCallbackManager(BaseCallbackManager):
"""Async dispatcher for graph lifecycle events."""
run_id: UUID | None
@property
def is_async(self) -> bool:
"""Return whether the manager is async."""
return True
def __init__(
self,
handlers: Sequence[GraphCallbackHandler] | None = None,
inheritable_handlers: Sequence[GraphCallbackHandler] | None = None,
parent_run_id: UUID | None = None,
*,
tags: list[str] | None = None,
inheritable_tags: list[str] | None = None,
metadata: dict[str, Any] | None = None,
inheritable_metadata: dict[str, Any] | None = None,
run_id: UUID | None = None,
) -> None:
_init_base_manager(
self,
handlers,
inheritable_handlers,
parent_run_id,
tags=tags,
inheritable_tags=inheritable_tags,
metadata=metadata,
inheritable_metadata=inheritable_metadata,
run_id=run_id,
)
def copy(
self,
*,
run_id: UUID | None | object = _MISSING,
) -> _AsyncGraphCallbackManager:
return _copy_graph_manager(self, _AsyncGraphCallbackManager, run_id)
@classmethod
def configure(
cls,
callbacks: object | None = None,
*,
run_id: UUID | None = None,
) -> _AsyncGraphCallbackManager:
return _configure_graph_callbacks(cls, callbacks, run_id=run_id)
async def on_interrupt(self, event: GraphInterruptEvent) -> None:
await ahandle_event(
self.handlers,
"on_interrupt",
None,
event,
)
async def on_resume(self, event: GraphResumeEvent) -> None:
await ahandle_event(
self.handlers,
"on_resume",
None,
event,
)
_GraphManagerT = TypeVar(
"_GraphManagerT", _GraphCallbackManager, _AsyncGraphCallbackManager
)
GraphCallbacks: TypeAlias = (
_GraphCallbackManager
| _AsyncGraphCallbackManager
| BaseCallbackManager
| GraphCallbackHandler
| Sequence[BaseCallbackHandler]
| Sequence[GraphCallbackHandler]
| None
)
def get_sync_graph_callback_manager_for_config(
config: RunnableConfig,
*,
run_id: UUID | None = None,
) -> _GraphCallbackManager:
"""Build a sync graph lifecycle callback manager from a runnable config.
This helper filters `config["callbacks"]` down to handlers that inherit
from `GraphCallbackHandler` and binds the provided `run_id` onto the
returned manager.
"""
return _GraphCallbackManager.configure(
config.get("callbacks"),
run_id=run_id,
)
def get_async_graph_callback_manager_for_config(
config: RunnableConfig,
*,
run_id: UUID | None = None,
) -> _AsyncGraphCallbackManager:
"""Build an async graph lifecycle callback manager from a runnable config.
This helper filters `config["callbacks"]` down to handlers that inherit
from `GraphCallbackHandler` and binds the provided `run_id` onto the
returned manager.
"""
return _AsyncGraphCallbackManager.configure(
config.get("callbacks"),
run_id=run_id,
)
@@ -1,6 +1,7 @@
from langgraph.channels.any_value import AnyValue
from langgraph.channels.base import BaseChannel
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.channels.delta import DeltaChannel
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.channels.last_value import LastValue, LastValueAfterFinish
from langgraph.channels.named_barrier_value import (
@@ -20,6 +21,7 @@ __all__ = (
"UntrackedValue",
"EphemeralValue",
"BinaryOperatorAggregate",
"DeltaChannel",
"NamedBarrierValue",
"NamedBarrierValueAfterFinish",
# topics
+16 -9
View File
@@ -22,10 +22,9 @@ __all__ = ("BinaryOperatorAggregate",)
def _strip_extras(t): # type: ignore[no-untyped-def]
"""Strips Annotated, Required and NotRequired from a given type."""
if hasattr(t, "__origin__"):
if t.__origin__ in (Required, NotRequired):
return _strip_extras(t.__args__[0])
return _strip_extras(t.__origin__)
if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
return _strip_extras(t.__args__[0])
return t
@@ -33,11 +32,22 @@ def _get_overwrite(value: Any) -> tuple[bool, Any]:
"""Inspects the given value and returns (is_overwrite, overwrite_value)."""
if isinstance(value, Overwrite):
return True, value.value
if isinstance(value, dict) and set(value.keys()) == {OVERWRITE}:
if isinstance(value, dict) and len(value) == 1 and OVERWRITE in value:
return True, value[OVERWRITE]
return False, None
def _operators_equal(a: Callable, b: Callable) -> bool:
"""Return True if two reducer operators should be considered equal.
Lambdas all share the name '<lambda>' so identity comparison is
unreliable; treat any pairing that includes a lambda as equal.
"""
if a.__name__ == "<lambda>" or b.__name__ == "<lambda>":
return True
return a is b
class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
"""Stores the result of applying a binary operator to the current value and each new value.
@@ -68,11 +78,8 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
self.value = MISSING
def __eq__(self, value: object) -> bool:
return isinstance(value, BinaryOperatorAggregate) and (
value.operator is self.operator
if value.operator.__name__ != "<lambda>"
and self.operator.__name__ != "<lambda>"
else True
return isinstance(value, BinaryOperatorAggregate) and _operators_equal(
self.operator, value.operator
)
@property
+197
View File
@@ -0,0 +1,197 @@
from __future__ import annotations
import collections.abc
import copy as _copy
from collections.abc import Callable, Sequence
from typing import Any, Generic
from langgraph.checkpoint.base import DELTA_SENTINEL, PendingWrite
from langgraph.checkpoint.serde.types import _DeltaSnapshot
from typing_extensions import Self
from langgraph._internal._typing import MISSING
from langgraph.channels.base import BaseChannel, Value
from langgraph.channels.binop import _get_overwrite, _operators_equal, _strip_extras
from langgraph.errors import (
EmptyChannelError,
ErrorCode,
InvalidUpdateError,
create_error_message,
)
__all__ = ("DeltaChannel",)
class DeltaChannel(Generic[Value], BaseChannel[Any, Any, Any]):
"""Reducer channel that stores only a sentinel in checkpoint blobs and
reconstructs state by replaying ancestor writes through the reducer.
The reducer receives the current accumulated value and a batch of writes
in one call: `reducer(state, [write1, write2, ...]) -> new_state`.
Reducers must be deterministic and batching-invariant (associative across
folds): applying two consecutive write batches separately must produce the
same state as applying their concatenation once:
reducer(reducer(state, xs), ys) == reducer(state, xs + ys)
This lets LangGraph replay checkpointed writes in larger batches than they
were originally produced without changing reconstructed state.
`snapshot_frequency=None` (default): pure delta; stores only
`DELTA_SENTINEL` in checkpoint blobs; reads replay all ancestor writes.
`snapshot_frequency=N`: `create_checkpoint` writes a full `_DeltaSnapshot`
blob every N steps, bounding replay depth to N.
Parameters:
reducer: `(state, list[writes]) -> new_state`. Must be deterministic
and batching-invariant as described above.
typ: The value type (e.g. `list`, `dict`). Inferred automatically
from the outer type when used inside `Annotated[T, DeltaChannel(...)]`.
snapshot_frequency: Every Nth pregel step writes a snapshot blob.
`None` (default) = pure delta, never snapshot.
"""
__slots__ = ("value", "reducer", "snapshot_frequency")
value: Value | Any
def __init__(
self,
reducer: Callable[[Any, Sequence[Any]], Any],
typ: type[Value] | None = None,
*,
snapshot_frequency: int | None = None,
) -> None:
if typ is None:
typ = list # type: ignore[assignment] # placeholder; overridden by _is_field_channel
super().__init__(typ)
self.reducer = reducer
self.snapshot_frequency = snapshot_frequency
typ = _strip_extras(typ)
if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
typ = list
if typ in (collections.abc.Set, collections.abc.MutableSet):
typ = set
if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
typ = dict
self.typ = typ
self.value: Any = MISSING
def __eq__(self, other: object) -> bool:
if not isinstance(other, DeltaChannel):
return False
if self.snapshot_frequency != other.snapshot_frequency:
return False
return _operators_equal(self.reducer, other.reducer)
@property
def ValueType(self) -> Any:
return self.typ
@property
def UpdateType(self) -> Any:
return self.typ
def is_snapshot_step(self, step: int) -> bool:
"""True if pregel should write a snapshot blob at this step."""
return (
self.snapshot_frequency is not None
and step > 0
and step % self.snapshot_frequency == 0
)
def copy(self) -> Self:
new = self.__class__(
self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
)
new.key = self.key
new.value = self.value if self.value is MISSING else _copy.copy(self.value)
return new
def from_checkpoint(self, checkpoint: Any) -> Self:
"""Initialize from a stored blob or sentinel.
Blob types (dispatched via serde ext code, not dict key inspection):
* `DELTA_SENTINEL` / `MISSING`: start empty; caller replays writes.
* `_DeltaSnapshot(value)`: restore value directly from snapshot.
* plain value (migration from old BinOp blobs): use directly.
"""
new = self.__class__(
self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
)
new.key = self.key
if checkpoint is MISSING or checkpoint is DELTA_SENTINEL:
new.value = self.typ()
elif isinstance(checkpoint, _DeltaSnapshot):
new.value = checkpoint.value
else:
new.value = checkpoint
return new
def replay_writes(self, writes: Sequence[PendingWrite]) -> None:
"""Apply ancestor writes oldest-to-newest via a single reducer call.
If any write is an Overwrite, the last one in the sequence acts as
the reset point: its value becomes the new base and only writes
after it are passed to the reducer.
"""
values = [v for _, _, v in writes]
if not values:
return
base = self.value
start = 0
for i, v in enumerate(values):
is_ow, ow_value = _get_overwrite(v)
if is_ow:
base = _copy.copy(ow_value) if ow_value is not None else self.typ()
start = i + 1
remaining = values[start:]
self.value = self.reducer(base, remaining) if remaining else base
def update(self, values: Sequence[Any]) -> bool:
if not values:
return False
overwrite_idx: int | None = None
for i, v in enumerate(values):
is_ow, _ = _get_overwrite(v)
if is_ow:
if overwrite_idx is not None:
msg = create_error_message(
message="Can receive only one Overwrite value per super-step.",
error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
)
raise InvalidUpdateError(msg)
overwrite_idx = i
if overwrite_idx is not None:
_, overwrite_value = _get_overwrite(values[overwrite_idx])
base = (
_copy.copy(overwrite_value)
if overwrite_value is not None
else self.typ()
)
remaining = [v for i, v in enumerate(values) if i != overwrite_idx]
self.value = self.reducer(base, remaining) if remaining else base
return True
base = self.typ() if self.value is MISSING else self.value
self.value = self.reducer(base, list(values))
return True
def get(self) -> Any:
if self.value is MISSING:
raise EmptyChannelError()
return self.value
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Any:
"""Return stored representation: always `DELTA_SENTINEL`.
Snapshot decisions are made by `create_checkpoint` in pregel (which
has the step number) via `is_snapshot_step`. `checkpoint()` is only
called for non-snapshot steps or when no checkpointer is available.
"""
if self.value is MISSING:
return MISSING
return DELTA_SENTINEL
+96 -5
View File
@@ -1,8 +1,9 @@
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from enum import Enum
from typing import Any
from typing import Any, Literal
from warnings import warn
# EmptyChannelError is re-exported from langgraph.channels.base
@@ -15,11 +16,14 @@ from langgraph.warnings import LangGraphDeprecatedSinceV10
__all__ = (
"EmptyChannelError",
"ErrorCode",
"GraphDrained",
"GraphRecursionError",
"InvalidUpdateError",
"GraphBubbleUp",
"GraphInterrupt",
"NodeError",
"NodeInterrupt",
"NodeTimeoutError",
"ParentCommand",
"EmptyInputError",
"TaskNotFound",
@@ -42,6 +46,23 @@ def create_error_message(*, message: str, error_code: ErrorCode) -> str:
)
class GraphBubbleUp(Exception):
pass
class GraphDrained(GraphBubbleUp):
"""Raised when a graph run exits early due to a drain request.
This indicates the graph stopped cooperatively at a superstep boundary
because `RunControl.request_drain()` was called (e.g., in response to
SIGTERM). The checkpoint is saved and the run can be resumed later.
"""
def __init__(self, reason: str = "shutdown") -> None:
self.reason = reason
super().__init__(f"Graph drained: {reason}")
class GraphRecursionError(RecursionError):
"""Raised when the graph has exhausted the maximum number of steps.
@@ -77,10 +98,6 @@ class InvalidUpdateError(Exception):
pass
class GraphBubbleUp(Exception):
pass
class GraphInterrupt(GraphBubbleUp):
"""Raised when a subgraph is interrupted, suppressed by the root graph.
Never raised directly, or surfaced to the user."""
@@ -125,3 +142,77 @@ class TaskNotFound(Exception):
"""Raised when the executor is unable to find a task (for distributed mode)."""
pass
@dataclass(frozen=True, slots=True)
class NodeError:
"""Failure context passed to a node-level error handler.
Inject by adding a parameter typed `NodeError` to a handler registered via
`StateGraph.add_node(..., error_handler=...)`:
```python
def handler(state: State, error: NodeError) -> Command:
return Command(update={"status": f"recovered from {error.node}: {error.error}"})
```
"""
node: str
"""Name of the node whose execution failed."""
error: BaseException
"""Exception raised by the failed node."""
class NodeTimeoutError(Exception):
"""Raised when a node invocation exceeds one of its configured timeouts.
Does **not** inherit from the built-in `TimeoutError` (a subclass of
`OSError`) so that the default `RetryPolicy` treats it as retryable.
Both `idle_timeout` and `run_timeout` reflect the configured policy at the
time of the failure (each is `None` if not configured). `kind` and
`timeout` identify which one fired.
"""
node: str
timeout: float
run_timeout: float | None
idle_timeout: float | None
elapsed: float
kind: Literal["idle", "run"]
def __init__(
self,
node: str,
elapsed: float,
*,
kind: Literal["idle", "run"],
idle_timeout: float | None = None,
run_timeout: float | None = None,
) -> None:
if kind == "idle":
if idle_timeout is None:
raise ValueError("idle_timeout is required when kind='idle'")
message = (
f"Node '{node}' exceeded its idle timeout of "
f"{idle_timeout:.3f}s without making progress "
f"(elapsed: {elapsed:.3f}s)."
)
self.timeout = idle_timeout
elif kind == "run":
if run_timeout is None:
raise ValueError("run_timeout is required when kind='run'")
message = (
f"Node '{node}' exceeded its run timeout of "
f"{run_timeout:.3f}s (elapsed: {elapsed:.3f}s)."
)
self.timeout = run_timeout
else:
raise ValueError("kind must be 'idle' or 'run'")
super().__init__(message)
self.node = node
self.elapsed = elapsed
self.kind = kind
self.idle_timeout = idle_timeout
self.run_timeout = run_timeout
+51 -6
View File
@@ -5,6 +5,7 @@ import inspect
import warnings
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import dataclass
from datetime import timedelta
from typing import (
Any,
Generic,
@@ -22,6 +23,11 @@ from typing_extensions import Unpack
from langgraph._internal import _serde
from langgraph._internal._constants import CACHE_NS_WRITES, PREVIOUS
from langgraph._internal._runnable import is_async_callable
from langgraph._internal._timeout import (
coerce_timeout_policy,
sync_timeout_unsupported,
)
from langgraph._internal._typing import MISSING, DeprecatedKwargs
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.channels.last_value import LastValue
@@ -31,13 +37,19 @@ from langgraph.pregel._call import (
P,
SyncAsyncFuture,
T,
call,
_call_with_options,
get_runnable_for_entrypoint,
identifier,
)
from langgraph.pregel._read import PregelNode
from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry
from langgraph.types import _DC_KWARGS, CachePolicy, RetryPolicy, StreamMode
from langgraph.types import (
_DC_KWARGS,
CachePolicy,
RetryPolicy,
StreamMode,
TimeoutPolicy,
)
from langgraph.typing import ContextT
from langgraph.warnings import LangGraphDeprecatedSinceV05, LangGraphDeprecatedSinceV10
@@ -51,6 +63,7 @@ class _TaskFunction(Generic[P, T]):
*,
retry_policy: Sequence[RetryPolicy],
cache_policy: CachePolicy[Callable[P, str | bytes]] | None = None,
timeout: TimeoutPolicy | None = None,
name: str | None = None,
) -> None:
if name is not None:
@@ -67,15 +80,17 @@ class _TaskFunction(Generic[P, T]):
self.func = func
self.retry_policy = retry_policy
self.cache_policy = cache_policy
self.timeout = timeout
functools.update_wrapper(self, func)
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> SyncAsyncFuture[T]:
return call(
return _call_with_options(
self.func,
args,
kwargs,
retry_policy=self.retry_policy,
cache_policy=self.cache_policy,
*args,
**kwargs,
timeout=self.timeout,
)
def clear_cache(self, cache: BaseCache) -> None:
@@ -98,6 +113,7 @@ def task(
name: str | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy[Callable[P, str | bytes]] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Callable[
[Callable[P, Awaitable[T]] | Callable[P, T]],
@@ -119,6 +135,7 @@ def task(
name: str | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy[Callable[P, str | bytes]] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> (
Callable[[Callable[P, Awaitable[T]] | Callable[P, T]], _TaskFunction[P, T]]
@@ -142,6 +159,14 @@ def task(
name: An optional name for the task. If not provided, the function name will be used.
retry_policy: An optional retry policy (or list of policies) to use for the task in case of a failure.
cache_policy: An optional cache policy to use for the task. This allows caching of the task results.
timeout: Timeout for each task attempt. A number or `timedelta` is a hard
wall-clock cap and is not refreshed. Use `TimeoutPolicy` to configure
both a wall-clock `run_timeout` and an `idle_timeout` refreshed by
progress signals. For long-running work that doesn't naturally emit
progress, call `runtime.heartbeat()` from inside the task. When the
timeout fires, `NodeTimeoutError` is raised and the retry policy (if
any) decides whether to retry. Supported only for async tasks; sync
tasks cannot be safely cancelled in-process.
Returns:
A callable function when used as a decorator.
@@ -196,6 +221,7 @@ def task(
)
if retry_policy is None:
retry_policy = retry # type: ignore[assignment]
timeout_policy = coerce_timeout_policy(timeout)
retry_policies: Sequence[RetryPolicy] = (
()
@@ -208,8 +234,15 @@ def task(
def decorator(
func: Callable[P, Awaitable[T]] | Callable[P, T],
) -> Callable[P, SyncAsyncFuture[T]]:
if timeout_policy is not None and not is_async_callable(func):
name_ = name or getattr(func, "__name__", func.__class__.__name__)
raise sync_timeout_unsupported(str(name_), kind="Task")
return _TaskFunction(
func, retry_policy=retry_policies, cache_policy=cache_policy, name=name
func,
retry_policy=retry_policies,
cache_policy=cache_policy,
timeout=timeout_policy,
name=name,
)
if __func_or_none__ is not None:
@@ -268,6 +301,15 @@ class entrypoint(Generic[ContextT]):
passed to the workflow.
cache_policy: A cache policy to use for caching the results of the workflow.
retry_policy: A retry policy (or list of policies) to use for the workflow in case of a failure.
timeout: Timeout for each workflow attempt. A number or `timedelta` is a
hard wall-clock cap and is not refreshed. Use `TimeoutPolicy` to
configure both a wall-clock `run_timeout` and an `idle_timeout`
refreshed by progress signals. For long-running work that doesn't
naturally emit progress, call `runtime.heartbeat()` from inside the
workflow. When the timeout fires, `NodeTimeoutError` is raised and
the retry policy (if any) decides whether to retry. Supported only
for async workflows; sync workflows cannot be safely cancelled
in-process.
!!! warning "`config_schema` Deprecated"
The `config_schema` parameter is deprecated in v0.6.0 and support will be removed in v2.0.0.
@@ -400,6 +442,7 @@ class entrypoint(Generic[ContextT]):
context_schema: type[ContextT] | None = None,
cache_policy: CachePolicy | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> None:
"""Initialize the entrypoint decorator."""
@@ -426,6 +469,7 @@ class entrypoint(Generic[ContextT]):
self.cache = cache
self.cache_policy = cache_policy
self.retry_policy = retry_policy
self.timeout = coerce_timeout_policy(timeout)
self.context_schema = context_schema
@dataclass(**_DC_KWARGS)
@@ -535,6 +579,7 @@ class entrypoint(Generic[ContextT]):
bound=bound,
triggers=[START],
channels=START,
timeout=self.timeout,
writers=[
ChannelWrite(
[
+4 -1
View File
@@ -9,7 +9,7 @@ from langgraph.store.base import BaseStore
from langgraph._internal._typing import EMPTY_SEQ
from langgraph.runtime import Runtime
from langgraph.types import CachePolicy, RetryPolicy, StreamWriter
from langgraph.types import CachePolicy, RetryPolicy, StreamWriter, TimeoutPolicy
from langgraph.typing import ContextT, NodeInputT, NodeInputT_contra
@@ -88,5 +88,8 @@ class StateNodeSpec(Generic[NodeInputT, ContextT]):
input_schema: type[NodeInputT]
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None
cache_policy: CachePolicy | None
is_error_handler: bool = False
error_handler_node: str | None = None
ends: tuple[str, ...] | dict[str, str] | None = EMPTY_SEQ
defer: bool = False
timeout: TimeoutPolicy | None = None
+65
View File
@@ -244,6 +244,71 @@ def add_messages(
return merged
def _messages_delta_reducer(
state: list[AnyMessage], writes: list[list[AnyMessage]]
) -> list[AnyMessage]:
"""**Experimental.** Batch reducer for use with `DeltaChannel`.
Processes all writes in one pass dedup by ID, `RemoveMessage`
tombstoning without calling `add_messages`.
This reducer is batching-invariant, as required by `DeltaChannel`:
`reducer(reducer(state, xs), ys) == reducer(state, xs + ys)`.
Raw dict / string / tuple inputs are coerced to typed `BaseMessage`
objects so that HTTP-driven graphs work without a separate coercion
step. This is not full `add_messages` parity `REMOVE_ALL_MESSAGES`,
unknown-id `RemoveMessage` errors, missing-id UUID assignment, and
`BaseMessageChunk` conversion are not handled here.
Example::
from typing import Annotated
from langgraph.channels.delta import DeltaChannel
from langgraph.graph.message import _messages_delta_reducer
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
"""
# Each write is either a list of message-likes or a single message-like
# (BaseMessage / dict / str / tuple). Only lists flatten; everything
# else is one message.
flat: list[Any] = []
for w in writes:
if isinstance(w, list):
flat.extend(w)
else:
flat.append(w)
# Steady state: the reducer's own output is already typed, so skip
# `convert_to_messages` on state when the first element is a BaseMessage.
# Only raw input (initial dicts, deserialized blobs) hits the slow path.
if state and isinstance(state[0], BaseMessage):
state_msgs = state
else:
state_msgs = cast("list[AnyMessage]", convert_to_messages(state))
msgs = cast("list[AnyMessage]", convert_to_messages(flat))
index: dict[str, int] = {
m.id: i for i, m in enumerate(state_msgs) if m.id is not None
}
result: list[AnyMessage | None] = list(state_msgs)
for msg in msgs:
mid = msg.id
if mid is None:
result.append(msg)
elif isinstance(msg, RemoveMessage):
if mid in index:
result[index[mid]] = None
del index[mid]
elif mid in index:
result[index[mid]] = msg
else:
index[mid] = len(result)
result.append(msg)
return [m for m in result if m is not None]
@deprecated(
"MessageGraph is deprecated in langgraph 1.0.0, to be removed in 2.0.0. Please use StateGraph with a `messages` key instead.",
category=None,
+82 -1
View File
@@ -7,6 +7,7 @@ import warnings
from collections import defaultdict
from collections.abc import Awaitable, Callable, Hashable, Sequence
from dataclasses import is_dataclass
from datetime import timedelta
from functools import partial
from inspect import isclass, isfunction, ismethod, signature
from types import FunctionType
@@ -45,9 +46,11 @@ from langgraph._internal._fields import (
)
from langgraph._internal._pydantic import create_model
from langgraph._internal._runnable import coerce_to_runnable
from langgraph._internal._timeout import coerce_timeout_policy
from langgraph._internal._typing import EMPTY_SEQ, MISSING, DeprecatedKwargs
from langgraph.channels.base import BaseChannel
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.channels.delta import DeltaChannel
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.channels.last_value import LastValue, LastValueAfterFinish
from langgraph.channels.named_barrier_value import (
@@ -81,6 +84,7 @@ from langgraph.types import (
Command,
RetryPolicy,
Send,
TimeoutPolicy,
ensure_valid_checkpointer,
)
from langgraph.typing import ContextT, InputT, NodeInputT, OutputT, StateT
@@ -299,7 +303,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema: None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
error_handler: StateNode[Any, ContextT] | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Self:
"""Add a new node to the `StateGraph`, input schema is inferred as the state schema.
@@ -366,7 +372,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema: type[NodeInputT],
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
error_handler: StateNode[Any, ContextT] | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Self:
"""Add a new node to the `StateGraph` where input schema is specified.
@@ -438,7 +446,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema: None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
error_handler: StateNode[Any, ContextT] | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Self:
"""Add a new node to the `StateGraph`, input schema is inferred as the state schema.
@@ -505,7 +515,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema: type[NodeInputT],
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
error_handler: StateNode[Any, ContextT] | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Self:
"""Add a new node to the `StateGraph`, input schema is specified.
@@ -579,7 +591,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema: type[NodeInputT] | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
error_handler: StateNode[Any, ContextT] | None = None,
destinations: dict[str, str] | tuple[str, ...] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Unpack[DeprecatedKwargs],
) -> Self:
"""Add a new node to the `StateGraph`.
@@ -598,6 +612,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
If a sequence is provided, the first matching policy will be applied.
cache_policy: The cache policy for the node.
error_handler: Optional node-level error handler callable for this node.
destinations: Destinations that indicate where a node can route to.
Useful for edgeless graphs with nodes that return `Command` objects.
@@ -609,6 +624,14 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
!!! warning
This is only used for graph rendering and doesn't have any effect on the graph execution.
timeout: Timeout for each node attempt. A number or `timedelta` is
a hard wall-clock cap and is not refreshed. Use `TimeoutPolicy`
to configure both a wall-clock `run_timeout` and an
`idle_timeout` refreshed by progress signals. When exceeded, a
[`NodeTimeoutError`][langgraph.errors.NodeTimeoutError] is raised
and the retry policy (if any) decides whether to retry. Timeouts
are supported only for async nodes; sync nodes cannot be safely
cancelled in-process.
Example:
```python
@@ -662,6 +685,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
)
if input_schema is None:
input_schema = cast(type[NodeInputT] | None, input_)
timeout = coerce_timeout_policy(timeout)
if not isinstance(node, str):
action = node
@@ -748,6 +772,25 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
if destinations is not None:
ends = destinations
resolved_input_schema: type[Any] = (
input_schema or inferred_input_schema or self.state_schema
)
handler_node_name: str | None = None
if error_handler is not None:
handler_node_name = f"__error_handler__{node}"
if handler_node_name in self.nodes:
raise ValueError(
f"Auto-generated error handler node `{handler_node_name}` already exists."
)
self.nodes[handler_node_name] = StateNodeSpec[Any, ContextT](
coerce_to_runnable(error_handler, name=handler_node_name, trace=False), # type: ignore[arg-type]
metadata=None,
input_schema=resolved_input_schema,
retry_policy=None,
cache_policy=None,
is_error_handler=True,
)
if input_schema is not None:
self.nodes[node] = StateNodeSpec[NodeInputT, ContextT](
coerce_to_runnable(action, name=node, trace=False), # type: ignore[arg-type]
@@ -755,8 +798,10 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema=input_schema,
retry_policy=retry_policy,
cache_policy=cache_policy,
error_handler_node=handler_node_name,
ends=ends,
defer=defer,
timeout=timeout,
)
elif inferred_input_schema is not None:
self.nodes[node] = StateNodeSpec(
@@ -765,8 +810,10 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema=inferred_input_schema,
retry_policy=retry_policy,
cache_policy=cache_policy,
error_handler_node=handler_node_name,
ends=ends,
defer=defer,
timeout=timeout,
)
else:
self.nodes[node] = StateNodeSpec[StateT, ContextT](
@@ -775,8 +822,10 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
input_schema=self.state_schema,
retry_policy=retry_policy,
cache_policy=cache_policy,
error_handler_node=handler_node_name,
ends=ends,
defer=defer,
timeout=timeout,
)
input_schema = input_schema or inferred_input_schema
@@ -1031,7 +1080,6 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
for node in interrupt:
if node not in self.nodes:
raise ValueError(f"Interrupt node `{node}` not found")
self.compiled = True
return self
@@ -1045,6 +1093,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
interrupt_after: All | list[str] | None = None,
debug: bool = False,
name: str | None = None,
transformers: Sequence[Callable[[tuple[str, ...]], Any]] | None = None,
) -> CompiledStateGraph[StateT, ContextT, InputT, OutputT]:
"""Compiles the `StateGraph` into a `CompiledStateGraph` object.
@@ -1077,11 +1126,19 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
interrupt_after: An optional list of node names to interrupt after.
debug: A flag indicating whether to enable debug mode.
name: The name to use for the compiled graph.
transformers: Optional sequence of `StreamTransformer` classes or
configured factories. Classes and factories are instantiated
per run whenever `stream_events(version="v3")` / `astream_events(version="v3")` is called and are
propagated to subgraph scopes. Custom factories should follow
the standard `StreamTransformer` constructor shape by
accepting `scope` as their first argument. Appended after the
built-in stream transformers.
Returns:
CompiledStateGraph: The compiled `StateGraph`.
"""
checkpointer = ensure_valid_checkpointer(checkpointer)
serde_allowlist: set[tuple[str, ...]] | None = None
if _serde.STRICT_MSGPACK_ENABLED:
schema_types: list[type[Any]] = [
@@ -1136,6 +1193,11 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
key for key, val in self.channels.items() if not is_managed_value(val)
]
)
node_error_handler_map = {
node_name: spec.error_handler_node
for node_name, spec in self.nodes.items()
if spec.error_handler_node is not None
}
compiled = CompiledStateGraph[StateT, ContextT, InputT, OutputT](
builder=self,
@@ -1158,7 +1220,9 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
debug=debug,
store=store,
cache=cache,
node_error_handler_map=node_error_handler_map,
name=name or "LangGraph",
stream_transformers=transformers,
)
compiled._serde_allowlist = serde_allowlist
@@ -1331,7 +1395,10 @@ class CompiledStateGraph(
metadata=node.metadata,
retry_policy=node.retry_policy,
cache_policy=node.cache_policy,
is_error_handler=node.is_error_handler,
error_handler_node=node.error_handler_node,
bound=node.runnable, # type: ignore[arg-type]
timeout=node.timeout,
)
else:
raise RuntimeError
@@ -1667,6 +1734,20 @@ def _is_field_channel(typ: type[Any]) -> BaseChannel | None:
# Search through all annotated medata to find channel annotations
for item in meta:
if isinstance(item, BaseChannel):
if isinstance(item, DeltaChannel) and hasattr(typ, "__origin__"):
origin = typ.__origin__
# Unwrap parameterized Required[X]/NotRequired[X] to X
# (e.g. Annotated[NotRequired[dict[...]], ...]).
if hasattr(origin, "__origin__") and origin.__origin__ in (
Required,
NotRequired,
):
origin = origin.__args__[0]
item = item.__class__(
item.reducer,
origin,
snapshot_frequency=item.snapshot_frequency,
)
return item
elif isclass(item) and issubclass(item, BaseChannel):
# ex, Annotated[int, EphemeralValue, SomeOtherAnnotation]
+205 -3
View File
@@ -39,6 +39,7 @@ from langgraph._internal._constants import (
CONFIG_KEY_CHECKPOINT_MAP,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_CHECKPOINTER,
CONFIG_KEY_NODE_ERROR,
CONFIG_KEY_READ,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_RUNTIME,
@@ -47,6 +48,7 @@ from langgraph._internal._constants import (
CONFIG_KEY_TASK_ID,
CONFIG_KEY_THREAD_ID,
ERROR,
ERROR_SOURCE_NODE,
INTERRUPT,
NO_WRITES,
NS_END,
@@ -66,6 +68,7 @@ from langgraph.channels.base import BaseChannel
from langgraph.channels.topic import Topic
from langgraph.channels.untracked_value import UntrackedValue
from langgraph.constants import TAG_HIDDEN
from langgraph.errors import NodeError
from langgraph.managed.base import ManagedValueMapping
from langgraph.pregel._call import get_runnable_for_task, identifier
from langgraph.pregel._io import read_channels
@@ -80,6 +83,7 @@ from langgraph.types import (
PregelTask,
RetryPolicy,
Send,
TimeoutPolicy,
)
GetNextVersion = Callable[[V | None, None], V]
@@ -114,13 +118,21 @@ class PregelTaskWrites(NamedTuple):
class Call:
__slots__ = ("func", "input", "retry_policy", "cache_policy", "callbacks")
__slots__ = (
"func",
"input",
"retry_policy",
"cache_policy",
"callbacks",
"timeout",
)
func: Callable
input: tuple[tuple[Any, ...], dict[str, Any]]
retry_policy: Sequence[RetryPolicy] | None
cache_policy: CachePolicy | None
callbacks: Callbacks
timeout: TimeoutPolicy | None
def __init__(
self,
@@ -130,12 +142,14 @@ class Call:
retry_policy: Sequence[RetryPolicy] | None,
cache_policy: CachePolicy | None,
callbacks: Callbacks,
timeout: TimeoutPolicy | None = None,
) -> None:
self.func = func
self.input = input
self.retry_policy = retry_policy
self.cache_policy = cache_policy
self.callbacks = callbacks
self.timeout = timeout
def should_interrupt(
@@ -281,7 +295,15 @@ def apply_writes(
pending_writes_by_channel: dict[str, list[Any]] = defaultdict(list)
for task in tasks:
for chan, val in task.writes:
if chan in (NO_WRITES, PUSH, RESUME, INTERRUPT, RETURN, ERROR):
if chan in (
NO_WRITES,
PUSH,
RESUME,
INTERRUPT,
RETURN,
ERROR,
ERROR_SOURCE_NODE,
):
pass
elif chan in channels:
pending_writes_by_channel[chan].append(val)
@@ -733,11 +755,48 @@ def prepare_single_task(
task_path[:3],
writers=proc.flat_writers,
subgraphs=proc.subgraphs,
timeout=proc.timeout,
)
else:
return PregelTask(task_id, name, task_path[:3])
def _coerce_pending_error(value: Any) -> BaseException:
if isinstance(value, BaseException):
return value
return Exception(str(value))
def _read_errors_from_pending_writes(
pending_writes: list[PendingWrite],
) -> list[BaseException]:
errors: list[BaseException] = []
for _, channel, value in pending_writes:
if channel == ERROR:
errors.append(_coerce_pending_error(value))
return errors
def _read_error_for_task_id_from_pending_writes(
pending_writes: list[PendingWrite], task_id: str
) -> BaseException | None:
for pending_task_id, channel, value in reversed(pending_writes):
if pending_task_id == task_id and channel == ERROR:
return _coerce_pending_error(value)
return None
def _read_error_source_node_from_pending_writes(
pending_writes: list[PendingWrite], task_id: str
) -> str | None:
for pending_task_id, channel, value in reversed(pending_writes):
if pending_task_id == task_id and channel == ERROR_SOURCE_NODE:
if isinstance(value, str):
return value
return str(value)
return None
def prepare_push_task_functional(
task_path: tuple[str, tuple, int, str, Call],
# (PUSH, parent task path, idx of PUSH write, id of parent task, Call)
@@ -870,6 +929,7 @@ def prepare_push_task_functional(
cache_key,
task_id,
in_progress_task_path,
timeout=call.timeout,
)
else:
return PregelTask(task_id, name, in_progress_task_path)
@@ -1041,11 +1101,153 @@ def prepare_push_task_send(
translated_task_path,
writers=proc.flat_writers,
subgraphs=proc.subgraphs,
timeout=packet.timeout if packet.timeout is not None else proc.timeout,
)
else:
return PregelTask(task_id, packet.node, translated_task_path)
def prepare_node_error_handler_task(
failed_task: PregelExecutableTask,
*,
handler_node_name: str,
failed_error: BaseException,
checkpoint: Checkpoint,
pending_writes: list[PendingWrite],
processes: Mapping[str, PregelNode],
channels: Mapping[str, BaseChannel],
managed: ManagedValueMapping,
config: RunnableConfig,
step: int,
stop: int,
store: BaseStore | None = None,
checkpointer: BaseCheckpointSaver | None = None,
manager: None | ParentRunManager | AsyncParentRunManager = None,
cache_policy: CachePolicy | None = None,
retry_policy: Sequence[RetryPolicy] = (),
) -> PregelExecutableTask | None:
"""Prepare an immediate node-level error handler task for a failed task."""
if handler_node_name not in processes:
return None
proc = processes[handler_node_name]
proc_node = proc.node
if proc_node is None:
return None
checkpoint_id_bytes = binascii.unhexlify(checkpoint["id"].replace("-", ""))
task_id_func = _xxhash_str if checkpoint["v"] > 1 else _uuid5_str
configurable = config.get(CONF, {})
parent_ns = configurable.get(CONFIG_KEY_CHECKPOINT_NS, "")
checkpoint_ns = (
f"{parent_ns}{NS_SEP}{handler_node_name}" if parent_ns else handler_node_name
)
task_id = task_id_func(
checkpoint_id_bytes,
checkpoint_ns,
str(step),
handler_node_name,
PUSH,
"node_error_handler",
failed_task.id,
)
task_checkpoint_ns = f"{checkpoint_ns}:{task_id}"
translated_task_path = (*failed_task.path[:3], "node_error_handler", False)
metadata = {
"langgraph_step": step,
"langgraph_node": handler_node_name,
"langgraph_triggers": PUSH_TRIGGER,
"langgraph_path": translated_task_path,
"langgraph_checkpoint_ns": task_checkpoint_ns,
}
if proc.metadata:
metadata.update(proc.metadata)
writes: deque[tuple[str, Any]] = deque()
effective_retry_policy = proc.retry_policy or retry_policy
effective_cache_policy = proc.cache_policy or cache_policy
if effective_cache_policy:
args_key = effective_cache_policy.key_func(failed_task.input)
cache_key = CacheKey(
(
CACHE_NS_WRITES,
(identifier(proc) or "__dynamic__"),
handler_node_name,
),
xxh3_128_hexdigest(
args_key.encode() if isinstance(args_key, str) else args_key
),
effective_cache_policy.ttl,
)
else:
cache_key = None
scratchpad = _scratchpad(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
)
runtime = cast(Runtime, configurable.get(CONFIG_KEY_RUNTIME, DEFAULT_RUNTIME))
runtime = runtime.override(
store=store, previous=checkpoint["channel_values"].get(PREVIOUS, None)
)
additional_config: RunnableConfig = {
"metadata": metadata,
"tags": proc.tags,
}
return PregelExecutableTask(
handler_node_name,
failed_task.input,
proc_node,
writes,
patch_config(
merge_configs(config, additional_config),
run_name=handler_node_name,
callbacks=manager.get_child(f"graph:step:{step}") if manager else None,
configurable={
CONFIG_KEY_TASK_ID: task_id,
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
scratchpad,
channels,
managed,
PregelTaskWrites(
translated_task_path,
handler_node_name,
writes,
PUSH_TRIGGER,
),
),
CONFIG_KEY_CHECKPOINTER: (
checkpointer or configurable.get(CONFIG_KEY_CHECKPOINTER)
),
CONFIG_KEY_CHECKPOINT_MAP: {
**configurable.get(CONFIG_KEY_CHECKPOINT_MAP, {}),
parent_ns: checkpoint["id"],
},
CONFIG_KEY_CHECKPOINT_ID: None,
CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns,
CONFIG_KEY_SCRATCHPAD: scratchpad,
CONFIG_KEY_RUNTIME: runtime,
CONFIG_KEY_NODE_ERROR: NodeError(
node=failed_task.name, error=failed_error
),
},
),
PUSH_TRIGGER,
effective_retry_policy,
cache_key,
task_id,
translated_task_path,
writers=proc.flat_writers,
subgraphs=proc.subgraphs,
)
def checkpoint_null_version(
checkpoint: Checkpoint,
) -> V | None:
@@ -1255,4 +1457,4 @@ def sanitize_untracked_values_in_send(
for k, v in packet.arg.items()
if not isinstance(channels.get(k), UntrackedValue)
}
return Send(node=packet.node, arg=sanitized_arg)
return Send(node=packet.node, arg=sanitized_arg, timeout=packet.timeout)
+30 -1
View File
@@ -8,6 +8,7 @@ import inspect
import sys
import types
from collections.abc import Awaitable, Callable, Generator, Sequence
from datetime import timedelta
from typing import Any, Generic, TypeVar, cast
from langchain_core.runnables import Runnable
@@ -20,9 +21,13 @@ from langgraph._internal._runnable import (
is_async_callable,
run_in_executor,
)
from langgraph._internal._timeout import (
coerce_timeout_policy,
sync_timeout_unsupported,
)
from langgraph.config import get_config
from langgraph.pregel._write import ChannelWrite, ChannelWriteEntry
from langgraph.types import CachePolicy, RetryPolicy
from langgraph.types import CachePolicy, RetryPolicy, TimeoutPolicy
##
# Utilities borrowed from cloudpickle.
@@ -255,8 +260,31 @@ def call(
*args: Any,
retry_policy: Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
**kwargs: Any,
) -> SyncAsyncFuture[T]:
return _call_with_options(
func,
args,
kwargs,
retry_policy=retry_policy,
cache_policy=cache_policy,
timeout=coerce_timeout_policy(timeout),
)
def _call_with_options(
func: Callable[P, Awaitable[T]] | Callable[P, T],
args: tuple[Any, ...],
kwargs: dict[str, Any],
*,
retry_policy: Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
timeout: TimeoutPolicy | None = None,
) -> SyncAsyncFuture[T]:
if timeout is not None and not is_async_callable(func):
name = getattr(func, "__name__", func.__class__.__name__)
raise sync_timeout_unsupported(name, kind="Task")
config = get_config()
impl = config[CONF][CONFIG_KEY_CALL]
fut = impl(
@@ -265,5 +293,6 @@ def call(
retry_policy=retry_policy,
cache_policy=cache_policy,
callbacks=config["callbacks"],
timeout=timeout,
)
return fut
+114 -16
View File
@@ -1,17 +1,23 @@
from __future__ import annotations
from collections.abc import Mapping
from collections.abc import Callable, Mapping
from datetime import datetime, timezone
from typing import Any, cast
from langgraph.checkpoint.base import Checkpoint
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import DELTA_SENTINEL, BaseCheckpointSaver, Checkpoint
from langgraph.checkpoint.base.id import uuid6
from langgraph.checkpoint.serde.types import _DeltaSnapshot
from langgraph._internal._typing import MISSING
from langgraph.channels.base import BaseChannel
from langgraph.channels.delta import DeltaChannel
from langgraph.managed.base import ManagedValueMapping, ManagedValueSpec
LATEST_VERSION = 4
GetNextVersion = Callable[[Any, None], Any]
def empty_checkpoint() -> Checkpoint:
return Checkpoint(
@@ -31,35 +37,87 @@ def create_checkpoint(
*,
id: str | None = None,
updated_channels: set[str] | None = None,
get_next_version: GetNextVersion | None = None,
force_delta_snapshot: bool = False,
) -> Checkpoint:
"""Create a checkpoint for the given channels."""
"""Create a checkpoint for the given channels.
For `DeltaChannel` with `snapshot_frequency=N`, snapshot steps write a
`_DeltaSnapshot` blob rather than `DELTA_SENTINEL`, bounding the ancestor
walk to at most N steps. Snapshots are eager: even if the channel had no
write this step, a version bump is forced (via `get_next_version`) so the
blob is stored by `put()`. Without `get_next_version` (e.g. static
contexts), snapshot steps gracefully fall back to sentinel.
`force_delta_snapshot` writes available `DeltaChannel` values as snapshots
regardless of `snapshot_frequency`. This is used by `durability="exit"`,
where intermediate writes are not stored as ancestor `checkpoint_writes`.
"""
ts = datetime.now(timezone.utc).isoformat()
if channels is None:
values = checkpoint["channel_values"]
channel_versions = checkpoint["channel_versions"]
else:
values = {}
channel_versions = dict(checkpoint["channel_versions"])
for k in channels:
if k not in checkpoint["channel_versions"]:
if k not in channel_versions:
continue
v = channels[k].checkpoint()
if v is not MISSING:
values[k] = v
ch = channels[k]
if (
isinstance(ch, DeltaChannel)
and (force_delta_snapshot or ch.is_snapshot_step(step))
and ch.is_available()
):
# Eager snapshot: bump version if not already written this step
# so put() includes this channel in new_versions and stores blob.
if get_next_version is not None and (
updated_channels is None or k not in updated_channels
):
channel_versions[k] = get_next_version(channel_versions[k], None)
values[k] = _DeltaSnapshot(ch.get())
else:
v = ch.checkpoint()
if v is not MISSING:
values[k] = v
return Checkpoint(
v=LATEST_VERSION,
ts=ts,
id=id or str(uuid6(clock_seq=step)),
channel_values=values,
channel_versions=checkpoint["channel_versions"],
channel_versions=channel_versions,
versions_seen=checkpoint["versions_seen"],
updated_channels=None if updated_channels is None else sorted(updated_channels),
)
def _needs_replay(spec: BaseChannel, stored: object) -> bool:
"""True if `spec` is a `DeltaChannel` and the stored blob is a sentinel,
requiring an ancestor walk to reconstruct.
`_DeltaSnapshot` blobs and plain values (migration) resolve directly via
`from_checkpoint` only `DELTA_SENTINEL` / `MISSING` trigger replay.
"""
if not isinstance(spec, DeltaChannel):
return False
return stored is MISSING or stored is DELTA_SENTINEL
def channels_from_checkpoint(
specs: Mapping[str, BaseChannel | ManagedValueSpec],
checkpoint: Checkpoint,
*,
saver: BaseCheckpointSaver | None = None,
config: RunnableConfig | None = None,
) -> tuple[Mapping[str, BaseChannel], ManagedValueMapping]:
"""Get channels from a checkpoint."""
"""Hydrate channels from a checkpoint.
For most channels, `spec.from_checkpoint(checkpoint["channel_values"][k])`
is sufficient. `DeltaChannel` is the exception: sentinel blobs require an
ancestor walk via `saver._get_channel_writes_history`. The walk terminates
at the nearest `_DeltaSnapshot` blob (step-based) or a pre-migration plain
value, so read depth is bounded by `snapshot_frequency`.
"""
channel_specs: dict[str, BaseChannel] = {}
managed_specs: dict[str, ManagedValueSpec] = {}
for k, v in specs.items():
@@ -67,13 +125,53 @@ def channels_from_checkpoint(
channel_specs[k] = v
else:
managed_specs[k] = v
return (
{
k: v.from_checkpoint(checkpoint["channel_values"].get(k, MISSING))
for k, v in channel_specs.items()
},
managed_specs,
)
channels: dict[str, BaseChannel] = {}
for k, spec in channel_specs.items():
ch: BaseChannel
stored = checkpoint["channel_values"].get(k, MISSING)
if _needs_replay(spec, stored) and saver is not None and config is not None:
delta_spec = cast(DeltaChannel, spec)
history = saver._get_channel_writes_history(config, k)
replay_ch = delta_spec.from_checkpoint(history.seed)
replay_ch.replay_writes(history.writes)
ch = replay_ch
else:
ch = spec.from_checkpoint(stored)
channels[k] = ch
return channels, managed_specs
async def achannels_from_checkpoint(
specs: Mapping[str, BaseChannel | ManagedValueSpec],
checkpoint: Checkpoint,
*,
saver: BaseCheckpointSaver | None = None,
config: RunnableConfig | None = None,
) -> tuple[Mapping[str, BaseChannel], ManagedValueMapping]:
"""Async version of `channels_from_checkpoint`. See docstring there."""
channel_specs: dict[str, BaseChannel] = {}
managed_specs: dict[str, ManagedValueSpec] = {}
for k, v in specs.items():
if isinstance(v, BaseChannel):
channel_specs[k] = v
else:
managed_specs[k] = v
channels: dict[str, BaseChannel] = {}
for k, spec in channel_specs.items():
ch: BaseChannel
stored = checkpoint["channel_values"].get(k, MISSING)
if _needs_replay(spec, stored) and saver is not None and config is not None:
delta_spec = cast(DeltaChannel, spec)
history = await saver._aget_channel_writes_history(config, k)
replay_ch = delta_spec.from_checkpoint(history.seed)
replay_ch.replay_writes(history.writes)
ch = replay_ch
else:
ch = spec.from_checkpoint(stored)
channels[k] = ch
return channels, managed_specs
def copy_checkpoint(checkpoint: Checkpoint) -> Checkpoint:
+235 -17
View File
@@ -45,11 +45,13 @@ from langgraph._internal._constants import (
CONFIG_KEY_REPLAY_STATE,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_RESUMING,
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_STREAM,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_THREAD_ID,
ERROR,
ERROR_SOURCE_NODE,
INPUT,
INTERRUPT,
NS_END,
@@ -62,7 +64,13 @@ from langgraph._internal._constants import (
from langgraph._internal._replay import ReplayState
from langgraph._internal._scratchpad import PregelScratchpad
from langgraph._internal._typing import EMPTY_SEQ, MISSING
from langgraph.callbacks import (
GraphInterruptEvent,
GraphLifecycleEvent,
GraphResumeEvent,
)
from langgraph.channels.base import BaseChannel
from langgraph.channels.delta import DeltaChannel
from langgraph.channels.untracked_value import UntrackedValue
from langgraph.constants import TAG_HIDDEN
from langgraph.errors import (
@@ -81,12 +89,14 @@ from langgraph.pregel._algo import (
checkpoint_null_version,
increment,
prepare_next_tasks,
prepare_node_error_handler_task,
prepare_single_task,
sanitize_untracked_values_in_send,
should_interrupt,
task_path_str,
)
from langgraph.pregel._checkpoint import (
achannels_from_checkpoint,
channels_from_checkpoint,
copy_checkpoint,
create_checkpoint,
@@ -112,11 +122,13 @@ from langgraph.pregel.debug import (
map_debug_tasks,
)
from langgraph.pregel.protocol import StreamChunk, StreamProtocol
from langgraph.runtime import RunControl, Runtime
from langgraph.types import (
All,
CachePolicy,
Command,
Durability,
Interrupt,
PregelExecutableTask,
RetryPolicy,
Send,
@@ -182,6 +194,8 @@ class PregelLoop:
_migrate_checkpoint: Callable[[Checkpoint], None] | None
submit: Submit
channels: Mapping[str, BaseChannel]
# Only set on AsyncPregelLoop; sync loops keep this as None.
_delta_write_futs: list[Any] | None = None
managed: ManagedValueMapping
checkpoint: Checkpoint
checkpoint_id_saved: str
@@ -196,13 +210,17 @@ class PregelLoop:
"input",
"pending",
"done",
"draining",
"interrupt_before",
"interrupt_after",
"out_of_steps",
]
control: RunControl | None
tasks: dict[str, PregelExecutableTask]
output: None | dict[str, Any] | Any = None
updated_channels: set[str] | None = None
_graph_lifecycle_events: deque[GraphLifecycleEvent]
_has_graph_lifecycle_callbacks: bool
# public
@@ -228,6 +246,7 @@ class PregelLoop:
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
retry_policy: Sequence[RetryPolicy] = (),
cache_policy: CachePolicy | None = None,
has_graph_lifecycle_callbacks: bool = False,
) -> None:
self.stream = stream
self.config = config
@@ -252,6 +271,8 @@ class PregelLoop:
self.retry_policy = retry_policy
self.cache_policy = cache_policy
self.durability = durability
self._has_graph_lifecycle_callbacks = has_graph_lifecycle_callbacks
self._graph_lifecycle_events = deque()
if self.stream is not None and CONFIG_KEY_STREAM in config[CONF]:
self.stream = DuplexStream(self.stream, config[CONF][CONFIG_KEY_STREAM])
scratchpad: PregelScratchpad | None = config[CONF].get(CONFIG_KEY_SCRATCHPAD)
@@ -302,6 +323,47 @@ class PregelLoop:
else ()
)
self.prev_checkpoint_config = None
runtime = self.config[CONF].get(CONFIG_KEY_RUNTIME)
self.control = runtime.control if isinstance(runtime, Runtime) else None
def _push_graph_lifecycle_event(
self,
kind: Literal["resume", "interrupt"],
*,
interrupts: tuple[Interrupt, ...] = (),
) -> None:
# drain status never reaches lifecycle events: tick() returns False
# before pushing, and interrupts are raised through GraphInterrupt
if self.status == "draining":
raise RuntimeError("Draining status cannot emit lifecycle events")
status = self.status
if kind == "resume":
self._graph_lifecycle_events.append(
GraphResumeEvent(
run_id=None,
status=status,
checkpoint_id=self.checkpoint["id"],
checkpoint_ns=self.checkpoint_ns,
)
)
elif kind == "interrupt":
self._graph_lifecycle_events.append(
GraphInterruptEvent(
run_id=None,
status=status,
checkpoint_id=self.checkpoint["id"],
checkpoint_ns=self.checkpoint_ns,
interrupts=interrupts,
)
)
else:
msg = f"Unknown graph lifecycle event type: {kind}"
raise AssertionError(msg)
def _pop_lifecycle_event(self) -> GraphLifecycleEvent | None:
if not self._graph_lifecycle_events:
return None
return self._graph_lifecycle_events.popleft()
def put_writes(self, task_id: str, writes: WritesT) -> None:
"""Put writes for a task, to be read by the next tick."""
@@ -361,7 +423,7 @@ class PregelLoop:
task = self.tasks.get(task_id)
else:
task = None
self.submit(
fut = self.submit(
self.checkpointer_put_writes,
config,
writes_to_save,
@@ -369,12 +431,16 @@ class PregelLoop:
task_path_str(task.path) if task else "",
)
else:
self.submit(
fut = self.submit(
self.checkpointer_put_writes,
config,
writes_to_save,
task_id,
)
if self._delta_write_futs is not None and any(
isinstance(self.specs.get(c), DeltaChannel) for c, _ in writes_to_save
):
self._delta_write_futs.append(fut)
# output writes
if hasattr(self, "tasks"):
self.output_writes(task_id, writes)
@@ -458,6 +524,16 @@ class PregelLoop:
# return the new task, to be started if not run before
return pushed
def schedule_error_handler(
self, failed_task: PregelExecutableTask, error: BaseException
) -> PregelExecutableTask | None:
raise NotImplementedError
async def aschedule_error_handler(
self, failed_task: PregelExecutableTask, error: BaseException
) -> PregelExecutableTask | None:
raise NotImplementedError
def tick(self) -> bool:
"""Execute a single iteration of the Pregel loop.
@@ -516,6 +592,10 @@ class PregelLoop:
self.status = "done"
return False
if self.control is not None and self.control.drain_requested:
self.status = "draining"
return False
# if there are pending writes from a previous loop, apply them
if not self.is_replaying and self.checkpoint_pending_writes:
self._match_writes(self.tasks)
@@ -582,7 +662,7 @@ class PregelLoop:
def _match_writes(self, tasks: Mapping[str, PregelExecutableTask]) -> None:
for tid, k, v in self.checkpoint_pending_writes:
if k in (ERROR, INTERRUPT, RESUME):
if k in (ERROR, ERROR_SOURCE_NODE, INTERRUPT, RESUME):
continue
if task := tasks.get(tid):
task.writes.append((k, v))
@@ -647,7 +727,7 @@ class PregelLoop:
# writes so that interrupt() calls re-fire instead of returning
# stale values. But if we're actively resuming, keep them —
# multi-interrupt scenarios need previously resolved values preserved.
if self.is_replaying and (
is_time_traveling = self.is_replaying and (
# Time-travel to a subgraph checkpoint: the parent sets
# RESUMING=True (it can't distinguish time-travel from resume),
# so we check if this subgraph's own ns is in checkpoint_map.
@@ -665,7 +745,8 @@ class PregelLoop:
# (subgraph input is a Send arg, not a Command)
or configurable.get(CONFIG_KEY_RESUMING, False)
)
):
)
if is_time_traveling:
self.checkpoint_pending_writes = [
w for w in self.checkpoint_pending_writes if w[1] != RESUME
]
@@ -720,6 +801,26 @@ class PregelLoop:
if k in self.checkpoint["channel_versions"]:
version = self.checkpoint["channel_versions"][k]
self.checkpoint["versions_seen"][INTERRUPT][k] = version
# When time-traveling (replaying from a specific checkpoint),
# save a fork checkpoint so the replayed execution creates a
# new branch. Without this, if the execution hits an interrupt
# before after_tick() runs, no new checkpoint is created —
# the parent's latest checkpoint remains the old one and
# subsequent resumes load the wrong state.
# Skip for update_state forks (source=update/fork) since they
# already have their own fork checkpoint.
if is_time_traveling and self.checkpoint_metadata.get("source") not in (
"update",
"fork",
):
# Clear old INTERRUPT writes from the loaded checkpoint.
# The fork will have a new checkpoint_id which changes
# task IDs — stale interrupt writes would accumulate and
# confuse the multiple-interrupt check in future resumes.
self.checkpoint_pending_writes = [
w for w in self.checkpoint_pending_writes if w[1] != INTERRUPT
]
self._put_checkpoint({"source": "fork"})
# produce values output
self._emit(
"values", map_output_values, self.output_keys, True, self.channels
@@ -762,14 +863,28 @@ class PregelLoop:
if not self.is_nested:
# Pass the resolved before-bound checkpoint ID so subgraphs can
# find their corresponding checkpoint without re-fetching the
# parent. For forks (source=update), use the fork's parent
# parent. For forks (source=update/fork), use the fork's parent
# checkpoint ID since the fork was created after the subgraph's
# checkpoints from the original execution.
#
# Only gate on is_time_traveling (not is_replaying). When the
# client resumes with an explicit checkpoint_id that happens to
# point at the current head (e.g. LangGraph Studio sending
# `checkpoint: {checkpoint_id}` alongside Command(resume=...)),
# is_replaying is True but is_time_traveling is False. In that
# case subgraphs should load their latest checkpoint normally,
# not go through ReplayState's before-bound lookup which would
# miss subgraph checkpoints created during processing of the
# current parent step.
replay_state: ReplayState | None = None
if self.is_replaying:
if is_time_traveling:
replay_checkpoint_id = self.checkpoint["id"]
if (
self.checkpoint_metadata.get("source") == "update"
self.checkpoint_metadata.get("source")
in (
"update",
"fork",
)
and self.prev_checkpoint_config
):
replay_checkpoint_id = self.prev_checkpoint_config[CONF].get(
@@ -785,6 +900,8 @@ class PregelLoop:
)
# set flag
self.status = "pending"
if is_resuming:
self._push_graph_lifecycle_event("resume")
return updated_channels
def _put_checkpoint(self, metadata: CheckpointMetadata) -> None:
@@ -808,6 +925,10 @@ class PregelLoop:
self.step,
id=self.checkpoint["id"] if exiting else None,
updated_channels=self.updated_channels,
get_next_version=self.checkpointer_get_next_version
if do_checkpoint
else None,
force_delta_snapshot=exiting and self.durability == "exit",
)
# sanitize TASK channel in the checkpoint before saving (durability=="exit")
if TASKS in self.checkpoint["channel_values"] and any(
@@ -885,8 +1006,10 @@ class PregelLoop:
self._put_checkpoint(self.checkpoint_metadata)
self._put_pending_writes()
# suppress interrupt
suppress = isinstance(exc_value, GraphInterrupt) and not self.is_nested
if suppress:
if isinstance(exc_value, GraphInterrupt) and not self.is_nested:
interrupt = exc_value
interrupts = tuple(interrupt.args[0]) if interrupt.args else ()
self._push_graph_lifecycle_event("interrupt", interrupts=interrupts)
# emit one last "values" event, with pending writes applied
if (
hasattr(self, "tasks")
@@ -913,12 +1036,11 @@ class PregelLoop:
self.channels,
)
# emit INTERRUPT if exception is empty (otherwise emitted by put_writes)
if exc_value is not None and (not exc_value.args or not exc_value.args[0]):
if not interrupt.args or not interrupt.args[0]:
interrupt_payload = interrupt.args[0] if interrupt.args else ()
self._emit(
"updates",
lambda: iter(
[{INTERRUPT: cast(GraphInterrupt, exc_value).args[0]}]
),
lambda: iter([{INTERRUPT: interrupt_payload}]),
)
# save final output
self.output = read_channels(self.channels, self.output_keys)
@@ -1040,6 +1162,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
retry_policy: Sequence[RetryPolicy] = (),
cache_policy: CachePolicy | None = None,
has_graph_lifecycle_callbacks: bool = False,
) -> None:
super().__init__(
input,
@@ -1061,6 +1184,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
retry_policy=retry_policy,
cache_policy=cache_policy,
durability=durability,
has_graph_lifecycle_callbacks=has_graph_lifecycle_callbacks,
)
self.stack = ExitStack()
if checkpointer:
@@ -1115,6 +1239,45 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
self.output_writes(task.id, task.writes, cached=True)
return pushed
def schedule_error_handler(
self, failed_task: PregelExecutableTask, error: BaseException
) -> PregelExecutableTask | None:
handler_node = self.nodes[failed_task.name].error_handler_node
if not handler_node:
return None
writes = list(failed_task.writes)
writes.append((ERROR_SOURCE_NODE, failed_task.name))
self.put_writes(
failed_task.id,
writes,
)
handler_task = prepare_node_error_handler_task(
failed_task,
handler_node_name=handler_node,
failed_error=error,
checkpoint=self.checkpoint,
pending_writes=self.checkpoint_pending_writes,
processes=self.nodes,
channels=self.channels,
managed=self.managed,
config=failed_task.config,
step=self.step,
stop=self.stop,
store=self.store,
checkpointer=self.checkpointer,
manager=self.manager,
retry_policy=self.retry_policy,
cache_policy=self.cache_policy,
)
if handler_task is None:
return None
self.tasks[handler_task.id] = handler_task
if not self.is_replaying:
self._match_writes({handler_task.id: handler_task})
for task in self.match_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return handler_task
def put_writes(self, task_id: str, writes: WritesT) -> None:
"""Put writes for a task, to be read by the next tick."""
super().put_writes(task_id, writes)
@@ -1136,6 +1299,7 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
# context manager
def __enter__(self) -> Self:
self._graph_lifecycle_events = deque()
if not self.checkpointer:
saved = None
elif self.checkpoint_config[CONF].get(CONFIG_KEY_CHECKPOINT_ID):
@@ -1187,7 +1351,10 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
)
self.submit = self.stack.enter_context(BackgroundExecutor(self.config))
self.channels, self.managed = channels_from_checkpoint(
self.specs, self.checkpoint
self.specs,
self.checkpoint,
saver=self.checkpointer,
config=self.checkpoint_config,
)
self.stack.push(self._suppress_interrupt)
self.status = "input"
@@ -1236,6 +1403,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
migrate_checkpoint: Callable[[Checkpoint], None] | None = None,
retry_policy: Sequence[RetryPolicy] = (),
cache_policy: CachePolicy | None = None,
has_graph_lifecycle_callbacks: bool = False,
) -> None:
super().__init__(
input,
@@ -1257,6 +1425,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
retry_policy=retry_policy,
cache_policy=cache_policy,
durability=durability,
has_graph_lifecycle_callbacks=has_graph_lifecycle_callbacks,
)
self.stack = AsyncExitStack()
if checkpointer:
@@ -1280,6 +1449,11 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
metadata: CheckpointMetadata,
new_versions: ChannelVersions,
) -> RunnableConfig:
# Drain DeltaChannel write futures before committing the checkpoint so
# DELTA_SENTINEL blobs are never saved ahead of their backing writes.
if self._delta_write_futs:
futs, self._delta_write_futs = self._delta_write_futs, []
await asyncio.gather(*futs)
try:
if prev is not None:
await prev
@@ -1311,6 +1485,45 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
self.output_writes(task.id, task.writes, cached=True)
return pushed
async def aschedule_error_handler(
self, failed_task: PregelExecutableTask, error: BaseException
) -> PregelExecutableTask | None:
handler_node = self.nodes[failed_task.name].error_handler_node
if not handler_node:
return None
writes = list(failed_task.writes)
writes.append((ERROR_SOURCE_NODE, failed_task.name))
self.put_writes(
failed_task.id,
writes,
)
handler_task = prepare_node_error_handler_task(
failed_task,
handler_node_name=handler_node,
failed_error=error,
checkpoint=self.checkpoint,
pending_writes=self.checkpoint_pending_writes,
processes=self.nodes,
channels=self.channels,
managed=self.managed,
config=failed_task.config,
step=self.step,
stop=self.stop,
store=self.store,
checkpointer=self.checkpointer,
manager=self.manager,
retry_policy=self.retry_policy,
cache_policy=self.cache_policy,
)
if handler_task is None:
return None
self.tasks[handler_task.id] = handler_task
if not self.is_replaying:
self._match_writes({handler_task.id: handler_task})
for task in await self.amatch_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return handler_task
def put_writes(self, task_id: str, writes: WritesT) -> None:
"""Put writes for a task, to be read by the next tick."""
super().put_writes(task_id, writes)
@@ -1335,6 +1548,7 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
# context manager
async def __aenter__(self) -> Self:
self._graph_lifecycle_events = deque()
if not self.checkpointer:
saved = None
elif self.checkpoint_config[CONF].get(CONFIG_KEY_CHECKPOINT_ID):
@@ -1384,11 +1598,15 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
if saved.pending_writes is not None
else []
)
self._delta_write_futs = []
self.submit = await self.stack.enter_async_context(
AsyncBackgroundExecutor(self.config)
)
self.channels, self.managed = channels_from_checkpoint(
self.specs, self.checkpoint
self.channels, self.managed = await achannels_from_checkpoint(
self.specs,
self.checkpoint,
saver=self.checkpointer,
config=self.checkpoint_config,
)
self.stack.push(self._suppress_interrupt)
self.status = "input"
@@ -24,6 +24,11 @@ try:
except ImportError:
_StreamingCallbackHandler = object # type: ignore
try:
from langchain_core.tracers._streaming import _V2StreamingCallbackHandler
except ImportError:
_V2StreamingCallbackHandler = object # type: ignore
T = TypeVar("T")
Meta = tuple[tuple[str, ...], dict[str, Any]]
@@ -248,3 +253,126 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler):
**kwargs: Any,
) -> Any:
self.metadata.pop(run_id, None)
class StreamMessagesHandlerV2(StreamMessagesHandler, _V2StreamingCallbackHandler):
"""v2 variant of `StreamMessagesHandler`.
Declaring `_V2StreamingCallbackHandler` as a base flips
`BaseChatModel.invoke` to route through `_stream_chat_model_events`
(firing `on_stream_event`) instead of `_stream` (firing
`on_llm_new_token`). Inherits `on_stream_event` from the parent,
which forwards protocol events onto the messages stream channel.
Pregel attaches this class instead of the v1 handler only when
`StreamingHandler` opts in via the internal
`CONFIG_KEY_STREAM_MESSAGES_V2` config key; direct
`graph.stream(stream_mode="messages")` callers keep the v1
AIMessageChunk shape.
"""
def on_llm_new_token(
self,
token: str,
*,
chunk: ChatGenerationChunk | None = None,
run_id: UUID,
parent_run_id: UUID | None = None,
tags: list[str] | None = None,
**kwargs: Any,
) -> Any:
"""Intentional no-op — v1 chunks are not used on v2-flagged runs.
The v2 marker already steers `invoke` to the event generator, so
`on_llm_new_token` should not fire under normal routing. This
override stays a pass-through (no call to `super()`) to make
the intent explicit and to guard against any caller (e.g. a
node that calls `model.stream()` directly, which still fires
the v1 callback) leaking AIMessageChunks onto a v2-flagged
messages stream.
"""
# Intentionally empty: v2 handler does not forward v1 chunks.
def __init__(
self,
stream: Callable[[StreamChunk], None],
subgraphs: bool,
*,
parent_ns: tuple[str, ...] | None = None,
) -> None:
super().__init__(stream, subgraphs, parent_ns=parent_ns)
self._streamed_run_ids: set[UUID] = set()
def on_llm_end(
self,
response: LLMResult,
*,
run_id: UUID,
parent_run_id: UUID | None = None,
**kwargs: Any,
) -> Any:
if meta := self.metadata.get(run_id):
if response.generations and response.generations[0]:
gen = response.generations[0][0]
if isinstance(gen, ChatGeneration):
if run_id in self._streamed_run_ids:
if gen.message.id is None:
gen.message.id = str(uuid4())
self.seen.add(gen.message.id)
else:
self._emit(meta, gen.message, dedupe=True)
self._streamed_run_ids.discard(run_id)
self.metadata.pop(run_id, None)
def on_llm_error(
self,
error: BaseException,
*,
run_id: UUID,
parent_run_id: UUID | None = None,
**kwargs: Any,
) -> Any:
self._streamed_run_ids.discard(run_id)
super().on_llm_error(
error,
run_id=run_id,
parent_run_id=parent_run_id,
**kwargs,
)
def on_stream_event(
self,
event: dict[str, Any],
*,
run_id: UUID,
parent_run_id: UUID | None = None,
tags: list[str] | None = None,
**kwargs: Any,
) -> Any:
"""Forward a protocol event from `stream_events(version="v3")` as a messages stream part.
Fires once per `MessagesData` event (`message-start`, per-block
`content-block-*`, `message-finish`). The transformer layer
correlates events back to a single `ChatModelStream` via
`metadata["run_id"]` attached here so the v1
`stream_mode="messages"` output (which emits
`(AIMessageChunk, metadata)` via `on_llm_new_token`) keeps its
original metadata shape.
Lives on the v2 handler rather than the v1 base: content-block
events are a v2-only concept, and forwarding them only when the
v2 handler is attached keeps the message channel's shape
predictable for v1 callers.
"""
if meta := self.metadata.get(run_id):
# Record message_id on message-start so on_chain_end's
# dedupe skips the finalized AIMessage the node returns
# (otherwise the messages projection double-counts: once
# from streaming, once from the chain output).
if event.get("event") == "message-start":
self._streamed_run_ids.add(run_id)
msg_id = event.get("message_id")
if msg_id:
self.seen.add(msg_id)
v2_meta = {**meta[1], "run_id": str(run_id)}
self.stream((meta[0], "messages", (event, v2_meta)))
+22 -1
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence
from datetime import timedelta
from functools import cached_property
from typing import (
Any,
@@ -11,10 +12,11 @@ from langchain_core.runnables import Runnable, RunnableConfig
from langgraph._internal._config import merge_configs
from langgraph._internal._constants import CONF, CONFIG_KEY_READ
from langgraph._internal._runnable import RunnableCallable, RunnableSeq
from langgraph._internal._timeout import coerce_timeout_policy
from langgraph.pregel._utils import find_subgraph_pregel
from langgraph.pregel._write import ChannelWrite
from langgraph.pregel.protocol import PregelProtocol
from langgraph.types import CachePolicy, RetryPolicy
from langgraph.types import CachePolicy, RetryPolicy, TimeoutPolicy
READ_TYPE = Callable[[str | Sequence[str], bool], Any | dict[str, Any]]
INPUT_CACHE_KEY_TYPE = tuple[Callable[..., Any], tuple[str, ...]]
@@ -123,12 +125,25 @@ class PregelNode:
cache_policy: CachePolicy | None
"""The cache policy to use when invoking the node."""
timeout: TimeoutPolicy | None
"""Timeout policy for a single invocation.
If exceeded, `NodeTimeoutError` is raised and the retry policy (if any)
decides whether to retry. Supported only for async nodes.
"""
tags: Sequence[str] | None
"""Tags to attach to the node for tracing."""
metadata: Mapping[str, Any] | None
"""Metadata to attach to the node for tracing."""
is_error_handler: bool
"""Whether this node is registered as an error handler node."""
error_handler_node: str | None
"""Optional handler node name for failures from this node."""
subgraphs: Sequence[PregelProtocol]
"""Subgraphs used by the node."""
@@ -144,7 +159,10 @@ class PregelNode:
bound: Runnable[Any, Any] | None = None,
retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
is_error_handler: bool = False,
error_handler_node: str | None = None,
subgraphs: Sequence[PregelProtocol] | None = None,
timeout: float | timedelta | TimeoutPolicy | None = None,
) -> None:
self.channels = channels
self.triggers = list(triggers)
@@ -156,8 +174,11 @@ class PregelNode:
self.retry_policy = (retry_policy,)
else:
self.retry_policy = retry_policy
self.timeout = coerce_timeout_policy(timeout)
self.tags = tags
self.metadata = metadata
self.is_error_handler = is_error_handler
self.error_handler_node = error_handler_node
if subgraphs is not None:
self.subgraphs = subgraphs
elif self.bound is not DEFAULT_BOUND:
+495 -15
View File
@@ -4,32 +4,487 @@ import asyncio
import logging
import random
import sys
import threading
import time
import weakref
from collections.abc import Awaitable, Callable, Sequence
from dataclasses import replace
from typing import Any
from contextlib import suppress
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
from typing import Any, Literal, NamedTuple
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.runnables import RunnableConfig
from langgraph._internal._config import patch_configurable, recast_checkpoint_ns
from langgraph._internal._config import (
merge_configs,
patch_configurable,
recast_checkpoint_ns,
)
from langgraph._internal._constants import (
CONF,
CONFIG_KEY_CALL,
CONFIG_KEY_CHECKPOINT_ID,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_RESUMING,
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SEND,
CONFIG_KEY_STREAM,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_THREAD_ID,
CONFIG_KEY_TIMED_ATTEMPT_OBSERVER,
NS_SEP,
)
from langgraph.errors import GraphBubbleUp, ParentCommand
from langgraph._internal._runnable import create_task_in_config_context
from langgraph._internal._timeout import sync_timeout_unsupported
from langgraph.errors import GraphBubbleUp, NodeTimeoutError, ParentCommand
from langgraph.pregel.protocol import StreamProtocol
from langgraph.runtime import ExecutionInfo, Runtime
from langgraph.types import Command, PregelExecutableTask, RetryPolicy
from langgraph.types import Command, PregelExecutableTask, RetryPolicy, TimeoutPolicy
logger = logging.getLogger(__name__)
SUPPORTS_EXC_NOTES = sys.version_info >= (3, 11)
def _timeout_secs(value: float | timedelta) -> float:
return value.total_seconds() if isinstance(value, timedelta) else value
@dataclass(frozen=True, slots=True)
class _ResolvedTimeout:
run_timeout_secs: float | None
idle_timeout_secs: float | None
refresh_on: Literal["auto", "heartbeat"] | None
def _resolve_timeout(timeout: TimeoutPolicy) -> _ResolvedTimeout:
idle_timeout_secs = (
_timeout_secs(timeout.idle_timeout)
if timeout.idle_timeout is not None
else None
)
return _ResolvedTimeout(
run_timeout_secs=(
_timeout_secs(timeout.run_timeout)
if timeout.run_timeout is not None
else None
),
idle_timeout_secs=idle_timeout_secs,
refresh_on=timeout.refresh_on if idle_timeout_secs is not None else None,
)
class _AttemptContext(NamedTuple):
"""Immutable per-attempt metadata shared across start/progress/finish events.
Built once at attempt start and referenced (not copied) by every emitted
`_AttemptEvent`, so per-event allocation is just the small event wrapper.
Intentionally underscore-prefixed: this and `_AttemptEvent` are part of an
internal observer contract consumed by langgraph-server. Do not move to
`langgraph.types` server imports them by this path.
"""
task_id: str
task_name: str
attempt: int
run_id: str | None
thread_id: str | None
checkpoint_ns: str | None
started_at: datetime
run_timeout_secs: float | None
idle_timeout_secs: float | None
refresh_on: Literal["auto", "heartbeat"] | None
@dataclass(frozen=True, slots=True)
class _AttemptEvent:
"""One lifecycle event for a timed attempt.
Holds a reference to the shared `_AttemptContext` and the event-specific
fields. The observer must treat this and `context` as read-only they
are reused across all events for the same attempt.
"""
context: _AttemptContext
event: Literal["start", "progress", "finish"]
progress_at: datetime | None = None
finished_at: datetime | None = None
status: Literal["success", "error"] | None = None
error_type: str | None = None
error_message: str | None = None
class _TimedAttemptScope:
"""Guarded-config window for timed attempts.
The wrapped config marks writes, stream events, runtime stream writer calls,
child task scheduling, and any LangChain callback event emitted under the
node's run as observable progress when `refresh_on="auto"`.
`runtime.heartbeat()` exposes a manual progress signal for work that doesn't
otherwise emit any of these, and is the only progress signal when
`refresh_on="heartbeat"`.
Guarded writes are serialized with `close()` so cancelled background tasks
cannot persist writes past the timeout boundary. Stream/custom output is
best-effort: it is dropped after close is observed, but callbacks run outside
the lock because they may contain arbitrary user/runtime code.
"""
__slots__ = (
"__weakref__",
"_active",
"_last_progress",
"_last_progress_emit",
"_lock",
"_on_progress",
"_progress_min_interval",
"_refresh_on",
)
def __init__(
self,
on_progress: Callable[[], None] | None = None,
progress_min_interval: float = 0.0,
refresh_on: Literal["auto", "heartbeat"] | None = None,
) -> None:
self._active = True
self._last_progress = time.monotonic()
self._lock = threading.Lock()
self._on_progress = on_progress
self._progress_min_interval = progress_min_interval
self._refresh_on = refresh_on
# `-inf` so the first touch always passes the rate-limit gate.
self._last_progress_emit: float = float("-inf")
def wrap_config(self, config: RunnableConfig) -> RunnableConfig:
configurable = config.get(CONF, {})
patch: dict[str, Any] = {}
if (send := configurable.get(CONFIG_KEY_SEND)) is not None:
patch[CONFIG_KEY_SEND] = self._guard_send(send)
if (stream := configurable.get(CONFIG_KEY_STREAM)) is not None:
patch[CONFIG_KEY_STREAM] = self._guard_stream(stream)
if (call := configurable.get(CONFIG_KEY_CALL)) is not None:
patch[CONFIG_KEY_CALL] = self._guard_call(call)
if isinstance(runtime := configurable.get(CONFIG_KEY_RUNTIME), Runtime):
if self._refresh_on is not None:
patch[CONFIG_KEY_RUNTIME] = runtime.override(
stream_writer=self._guard_stream_writer(runtime.stream_writer),
heartbeat=self.touch,
)
else:
patch[CONFIG_KEY_RUNTIME] = runtime.override(
stream_writer=self._guard_stream_writer(runtime.stream_writer)
)
new_config = patch_configurable(config, patch) if patch else config
if self._refresh_on == "auto":
return merge_configs(
new_config, {"callbacks": [_IdleProgressCallbackHandler(self)]}
)
return new_config
def touch(self) -> None:
# Avoid locking this hot progress path. We accept a small race window in
# timestamp ordering because idle_timeout is expected to be coarse compared
# with scheduler/thread timing.
now = time.monotonic()
self._last_progress = now
if self._on_progress is None:
return
# Best-effort rate limit: a benign race may emit a duplicate progress
# event under heavy concurrency, which observers must already tolerate
# (callbacks fire from arbitrary threads).
if now - self._last_progress_emit < self._progress_min_interval:
return
self._last_progress_emit = now
self._on_progress()
def close(self) -> None:
with self._lock:
self._active = False
async def wait_for_idle_timeout(self, idle_timeout_s: float) -> None:
while True:
with self._lock:
if not self._active:
return
remaining = self._last_progress + idle_timeout_s - time.monotonic()
if remaining <= 0:
raise asyncio.TimeoutError
await asyncio.sleep(remaining)
def _guard_send(
self, send: Callable[[Sequence[tuple[str, Any]]], None]
) -> Callable[[Sequence[tuple[str, Any]]], None]:
def guarded_send(writes: Sequence[tuple[str, Any]]) -> None:
with self._lock:
if self._active:
if writes and self._refresh_on == "auto":
self._last_progress = time.monotonic()
send(writes)
return guarded_send
def _guard_stream(self, stream: StreamProtocol) -> StreamProtocol:
# No lock: stream callbacks fire from the event loop only, so the
# active-check + write happen atomically between awaits.
def guarded_stream(chunk: tuple[tuple[str, ...], str, Any]) -> None:
if not self._active:
return
if self._refresh_on == "auto":
self._last_progress = time.monotonic()
stream(chunk)
return StreamProtocol(guarded_stream, stream.modes)
def _guard_call(self, call: Callable[..., Any]) -> Callable[..., Any]:
# No lock: child-task scheduling happens from the event loop only.
def guarded_call(*args: Any, **kwargs: Any) -> Any:
if not self._active:
raise asyncio.CancelledError
if self._refresh_on == "auto":
self._last_progress = time.monotonic()
return call(*args, **kwargs)
return guarded_call
def _guard_stream_writer(
self, stream_writer: Callable[[Any], None]
) -> Callable[[Any], None]:
def guarded_stream_writer(chunk: Any) -> None:
with self._lock:
if not self._active:
return
if self._refresh_on == "auto":
self._last_progress = time.monotonic()
stream_writer(chunk)
return guarded_stream_writer
class _IdleProgressCallbackHandler(BaseCallbackHandler):
"""Resets the idle timeout clock on any LangChain callback event.
Inherits via `config["callbacks"]`, so it sees only events emitted by
runs descended from the node's attempt — sibling nodes do not bleed
through. Holds the scope by weakref so a child manager that outlives
the attempt cannot keep the scope alive.
"""
# Run inline so progress is recorded in callback emission order;
# thread-pool dispatch would introduce extra reordering.
run_inline = True
def __init__(self, scope: _TimedAttemptScope) -> None:
self._scope_ref = weakref.ref(scope)
def _touch(self, *args: Any, **kwargs: Any) -> None:
if (scope := self._scope_ref()) is not None:
scope.touch()
on_llm_start = _touch
on_chat_model_start = _touch
on_llm_new_token = _touch
on_llm_end = _touch
on_llm_error = _touch
on_chain_start = _touch
on_chain_end = _touch
on_chain_error = _touch
on_tool_start = _touch
on_tool_end = _touch
on_tool_error = _touch
on_retriever_start = _touch
on_retriever_end = _touch
on_retriever_error = _touch
on_agent_action = _touch
on_agent_finish = _touch
on_text = _touch
on_retry = _touch
on_custom_event = _touch
def _drain_cancelled(task: asyncio.Task[Any]) -> None:
# Mark the abandoned task's exception as retrieved so asyncio doesn't log it.
with suppress(asyncio.CancelledError):
task.exception()
def _start_timed_attempt(
task: PregelExecutableTask, config: RunnableConfig, timeout: _ResolvedTimeout
) -> _AttemptContext | None:
configurable = config.get(CONF, {})
callback = configurable.get(CONFIG_KEY_TIMED_ATTEMPT_OBSERVER)
if callback is None:
return None
runtime = configurable.get(CONFIG_KEY_RUNTIME)
execution_info = runtime.execution_info if isinstance(runtime, Runtime) else None
context = _AttemptContext(
task_id=task.id,
task_name=task.name,
attempt=execution_info.node_attempt if execution_info is not None else 1,
run_id=execution_info.run_id if execution_info is not None else None,
thread_id=execution_info.thread_id if execution_info is not None else None,
checkpoint_ns=(
execution_info.checkpoint_ns if execution_info is not None else None
),
started_at=datetime.now(timezone.utc),
run_timeout_secs=timeout.run_timeout_secs,
idle_timeout_secs=timeout.idle_timeout_secs,
refresh_on=timeout.refresh_on,
)
_dispatch_observer(callback, _AttemptEvent(context=context, event="start"))
return context
def _finish_timed_attempt(
config: RunnableConfig,
context: _AttemptContext | None,
error: BaseException | None = None,
) -> None:
if context is None:
return
callback = config.get(CONF, {}).get(CONFIG_KEY_TIMED_ATTEMPT_OBSERVER)
if callback is None:
return
_dispatch_observer(
callback,
_AttemptEvent(
context=context,
event="finish",
finished_at=datetime.now(timezone.utc),
status="error" if error is not None else "success",
error_type=type(error).__name__ if error is not None else None,
error_message=str(error) if error is not None else None,
),
)
def _emit_progress(
callback: Callable[[_AttemptEvent], None],
context: _AttemptContext,
) -> None:
_dispatch_observer(
callback,
_AttemptEvent(
context=context,
event="progress",
progress_at=datetime.now(timezone.utc),
),
)
def _dispatch_observer(
callback: Callable[[_AttemptEvent], None],
event: _AttemptEvent,
) -> None:
try:
callback(event)
except Exception:
logger.warning("Timed attempt observer failed", exc_info=True)
async def _run_timeout_watchdog(run_timeout_s: float) -> None:
await asyncio.sleep(run_timeout_s)
raise asyncio.TimeoutError
async def _arun_with_timeout(
task: PregelExecutableTask,
config: RunnableConfig,
timeout: _ResolvedTimeout,
attempt_ctx: _AttemptContext | None,
*,
stream: bool,
) -> Any:
run_timeout_s = timeout.run_timeout_secs
idle_timeout_s = timeout.idle_timeout_secs
on_progress: Callable[[], None] | None = None
if attempt_ctx is not None:
callback = config.get(CONF, {}).get(CONFIG_KEY_TIMED_ATTEMPT_OBSERVER)
if callback is not None and idle_timeout_s is not None:
on_progress = lambda: _emit_progress(callback, attempt_ctx) # noqa: E731
scope = _TimedAttemptScope(
on_progress=on_progress,
# Cap progress emission at ~4 events per idle window so token-rate
# callbacks don't flood the observer.
progress_min_interval=idle_timeout_s / 4 if idle_timeout_s is not None else 0.0,
refresh_on=timeout.refresh_on,
)
scoped_config = scope.wrap_config(config)
start = time.monotonic()
if stream:
# Yielded chunks count as progress only under `refresh_on="auto"`.
# `refresh_on="heartbeat"` is the strict mode where only explicit
# `runtime.heartbeat()` calls reset the idle clock.
async def run() -> Any:
async for _ in task.proc.astream(task.input, scoped_config):
if timeout.refresh_on == "auto":
scope.touch()
else:
async def run() -> Any:
return await task.proc.ainvoke(task.input, scoped_config)
bg = create_task_in_config_context(run, scoped_config)
watchdogs: dict[asyncio.Task[None], Literal["idle", "run"]] = {}
if idle_timeout_s is not None:
watchdogs[asyncio.create_task(scope.wait_for_idle_timeout(idle_timeout_s))] = (
"idle"
)
if run_timeout_s is not None:
watchdogs[asyncio.create_task(_run_timeout_watchdog(run_timeout_s))] = "run"
try:
done, _ = await asyncio.wait(
{bg, *watchdogs}, return_when=asyncio.FIRST_COMPLETED
)
if bg in done:
# Task completed in time.
for watchdog in watchdogs:
watchdog.cancel()
# FIRST_COMPLETED can return both; a watchdog may have
# already raised TimeoutError before we cancelled it.
for watchdog in watchdogs:
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
await watchdog
return await bg
# bg was not in `done`, so every member of `done` is one of our
# watchdogs. Only a watchdog's TimeoutError converts to
# NodeTimeoutError; any TimeoutError raised by the proc itself
# propagates unchanged.
for watchdog in done:
kind = watchdogs[watchdog]
try:
await watchdog
except asyncio.TimeoutError as exc:
elapsed = time.monotonic() - start
scope.close()
task.writes.clear()
bg.cancel()
bg.add_done_callback(_drain_cancelled)
raise NodeTimeoutError(
task.name,
elapsed,
kind=kind,
idle_timeout=idle_timeout_s,
run_timeout=run_timeout_s,
) from exc
raise RuntimeError(
f"{kind} timeout watchdog completed without raising TimeoutError"
)
raise RuntimeError("timeout wait completed without task or watchdog")
except asyncio.CancelledError:
scope.close()
bg.cancel()
for watchdog in watchdogs:
watchdog.cancel()
bg.add_done_callback(_drain_cancelled)
raise
finally:
scope.close()
for watchdog in watchdogs:
watchdog.cancel()
def _ensure_execution_info(
runtime: Runtime, config: RunnableConfig, task: PregelExecutableTask
) -> Runtime:
@@ -90,6 +545,10 @@ def run_with_retry(
) -> None:
"""Run a task with retries."""
retry_policy = task.retry_policy or retry_policy
if task.timeout is not None:
# `validate_timeout_supported` catches sync nodes at compile time;
# this is a runtime safety net for paths that may bypass that validation.
raise sync_timeout_unsupported(task.name)
attempts = 0
node_first_attempt_time = time.time()
config = task.config
@@ -195,6 +654,9 @@ async def arun_with_retry(
) -> None:
"""Run a task asynchronously with retries."""
retry_policy = task.retry_policy or retry_policy
resolved_timeout = (
_resolve_timeout(task.timeout) if task.timeout is not None else None
)
attempts = 0
node_first_attempt_time = time.time()
config = task.config
@@ -229,35 +691,53 @@ async def arun_with_retry(
)
},
)
attempt_ctx = (
_start_timed_attempt(task, config, resolved_timeout)
if resolved_timeout is not None
else None
)
try:
# clear any writes from previous attempts
task.writes.clear()
# run the task
if resolved_timeout is None:
if stream:
async for _ in task.proc.astream(task.input, config):
pass
break
return await task.proc.ainvoke(task.input, config)
result = await _arun_with_timeout(
task, config, resolved_timeout, attempt_ctx, stream=stream
)
_finish_timed_attempt(config, attempt_ctx)
if stream:
async for _ in task.proc.astream(task.input, config):
pass
# if successful, end
break
else:
return await task.proc.ainvoke(task.input, config)
return result
except ParentCommand as exc:
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
cmd = exc.args[0]
# strip task_ids from namespace for comparison (ns format: "node1|node2:task_id")
if cmd.graph in (ns, recast_checkpoint_ns(ns), task.name):
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
try:
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
except Exception as writer_exc:
_finish_timed_attempt(config, attempt_ctx, writer_exc)
raise
_finish_timed_attempt(config, attempt_ctx)
break
elif cmd.graph == Command.PARENT:
# this command is for the parent graph, assign it to the parent.
exc.args = (replace(cmd, graph=_checkpoint_ns_for_parent_command(ns)),)
# bubble up
_finish_timed_attempt(config, attempt_ctx)
# bubble up the exception to the parent graph
raise
except GraphBubbleUp:
# if interrupted, end
_finish_timed_attempt(config, attempt_ctx)
raise
except Exception as exc:
_finish_timed_attempt(config, attempt_ctx, exc)
if SUPPORTS_EXC_NOTES:
exc.add_note(f"During task with name '{task.name}' and id '{task.id}'")
if not retry_policy:
+190 -18
View File
@@ -10,8 +10,10 @@ from collections.abc import (
AsyncIterator,
Awaitable,
Callable,
Collection,
Iterable,
Iterator,
Mapping,
Sequence,
)
from functools import partial
@@ -46,6 +48,7 @@ from langgraph.types import (
CachePolicy,
PregelExecutableTask,
RetryPolicy,
TimeoutPolicy,
)
F = TypeVar("F", concurrent.futures.Future, asyncio.Future)
@@ -71,6 +74,10 @@ SKIP_RERAISE_SET: weakref.WeakSet[concurrent.futures.Future | asyncio.Future] =
class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
event: E
callback: weakref.ref[Callable[[PregelExecutableTask, BaseException | None], None]]
# Stop condition is injected by PregelRunner instead of hard-coded here.
# This lets the runner treat graph-error-handled exceptions as non-fatal
# so `on_done` does not trigger an early stop for those futures.
should_stop: Callable[[set[F]], bool]
counter: int
done: set[F]
lock: threading.Lock
@@ -81,6 +88,7 @@ class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
callback: weakref.ref[
Callable[[PregelExecutableTask, BaseException | None], None]
],
should_stop: Callable[[set[F]], bool],
future_type: type[F],
# used for generic typing, newer py supports FutureDict[...](...)
) -> None:
@@ -88,6 +96,7 @@ class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
self.lock = threading.Lock()
self.event = event
self.callback = callback
self.should_stop = should_stop
self.counter = 0
self.done: set[F] = set()
@@ -108,6 +117,7 @@ class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
task: PregelExecutableTask,
fut: F,
) -> None:
# Called automatically by future.add_done_callback registered in __setitem__.
try:
if cb := self.callback():
cb(task, _exception(fut))
@@ -115,7 +125,9 @@ class FuturesDict(Generic[F, E], dict[F, PregelExecutableTask | None]):
with self.lock:
self.done.add(fut)
self.counter -= 1
if self.counter == 0 or _should_stop_others(self.done):
# Wake waiter when all tracked futures are done, or when runner-level
# stop condition is met (for example, a non-handled fatal exception).
if self.counter == 0 or self.should_stop(self.done):
self.event.set()
@@ -131,11 +143,34 @@ class PregelRunner:
put_writes: weakref.ref[Callable[[str, Sequence[tuple[str, Any]]], None]],
use_astream: bool = False,
node_finished: Callable[[str], None] | None = None,
node_error_handler_map: Mapping[str, str] | None = None,
schedule_error_handler: Callable[
[PregelExecutableTask, BaseException], PregelExecutableTask | None
]
| None = None,
aschedule_error_handler: Callable[
[PregelExecutableTask, BaseException],
Awaitable[PregelExecutableTask | None],
]
| None = None,
) -> None:
self.submit = submit
self.put_writes = put_writes
self.use_astream = use_astream
self.node_finished = node_finished
self.node_error_handler_map = dict(node_error_handler_map or {})
self.error_handler_nodes = set(self.node_error_handler_map.values())
self.schedule_error_handler = schedule_error_handler
self.aschedule_error_handler = aschedule_error_handler
# Exception object ids that are already routed to graph-level error handler.
# These ids are consulted by stop/panic checks to avoid re-raising handled
# exceptions via the normal fatal path in the same run.
self._handled_exception_ids: set[int] = set()
def _should_route_to_error_handler(self, task: PregelExecutableTask) -> bool:
if task.name in self.error_handler_nodes:
return False
return task.name in self.node_error_handler_map
def tick(
self,
@@ -154,6 +189,9 @@ class PregelRunner:
futures = FuturesDict(
callback=weakref.WeakMethod(self.commit),
event=threading.Event(),
should_stop=partial(
_should_stop_others, handled_exception_ids=self._handled_exception_ids
),
future_type=concurrent.futures.Future,
)
# give control back to the caller
@@ -163,6 +201,7 @@ class PregelRunner:
return
elif len(tasks) == 1 and timeout is None and get_waiter is None:
t = tasks[0]
scheduled_error_handler = False
try:
run_with_retry(
t,
@@ -181,12 +220,23 @@ class PregelRunner:
self.commit(t, None)
except Exception as exc:
self.commit(t, exc)
if (
not isinstance(exc, GraphBubbleUp)
and self._should_route_to_error_handler(t)
and self.schedule_error_handler is not None
):
self._handled_exception_ids.add(id(exc))
if handler_task := self.schedule_error_handler(t, exc):
tasks = (handler_task,)
scheduled_error_handler = True
# Continue to the regular scheduling path for handler execution.
if reraise and futures:
# will be re-raised after futures are done
fut: concurrent.futures.Future = concurrent.futures.Future()
fut.set_exception(exc)
futures.done.add(fut)
elif reraise:
if id(exc) not in self._handled_exception_ids:
# will be re-raised after futures are done
fut: concurrent.futures.Future = concurrent.futures.Future()
fut.set_exception(exc)
futures.done.add(fut)
elif reraise and id(exc) not in self._handled_exception_ids:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
@@ -195,10 +245,12 @@ class PregelRunner:
tb = tb.tb_next
exc.__traceback__ = tb
raise
if not futures: # maybe `t` scheduled another task
if not futures and not scheduled_error_handler:
# maybe `t` scheduled another task
return
else:
tasks = () # don't reschedule this task
if not scheduled_error_handler:
tasks = () # don't reschedule this task
# add waiter task if requested
if get_waiter is not None:
futures[get_waiter()] = None
@@ -225,6 +277,7 @@ class PregelRunner:
# each task is independent from all other concurrent tasks
# yield updates/debug output as each task finishes
end_time = timeout + time.monotonic() if timeout else None
handled_futures: set[concurrent.futures.Future[Any]] = set()
while len(futures) > (1 if get_waiter is not None else 0):
done, inflight = concurrent.futures.wait(
futures,
@@ -233,17 +286,49 @@ class PregelRunner:
)
if not done:
break # timed out
done_for_stop: set[concurrent.futures.Future[Any]] = set()
for fut in done:
task = futures.pop(fut)
if task is None:
# waiter task finished, schedule another
if inflight and get_waiter is not None:
futures[get_waiter()] = None
elif (
(task_exc := _exception(fut))
and self._should_route_to_error_handler(task)
and not isinstance(task_exc, GraphBubbleUp)
):
self._handled_exception_ids.add(id(task_exc))
SKIP_RERAISE_SET.add(fut)
handled_futures.add(fut)
if self.schedule_error_handler is not None:
if handler_task := self.schedule_error_handler(task, task_exc):
handler_fut = self.submit()( # type: ignore[misc]
run_with_retry,
handler_task,
retry_policy,
configurable={
CONFIG_KEY_CALL: partial(
_call,
weakref.ref(handler_task),
retry_policy=retry_policy,
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
),
},
__reraise_on_exit__=reraise,
)
futures[handler_fut] = handler_task
else:
done_for_stop.add(fut)
else:
# remove references to loop vars
del fut, task
# maybe stop other tasks
if _should_stop_others(done):
if _should_stop_others(
done_for_stop, handled_exception_ids=self._handled_exception_ids
):
break
# give control back to the caller
yield
@@ -258,6 +343,8 @@ class PregelRunner:
_panic_or_proceed(
futures.done.union(f for f, t in futures.items() if t is not None),
panic=reraise,
handled_exception_ids=self._handled_exception_ids,
handled_futures=handled_futures,
)
except Exception as exc:
if tb := exc.__traceback__:
@@ -291,6 +378,9 @@ class PregelRunner:
futures = FuturesDict(
callback=weakref.WeakMethod(self.commit),
event=asyncio.Event(),
should_stop=partial(
_should_stop_others, handled_exception_ids=self._handled_exception_ids
),
future_type=asyncio.Future,
)
# give control back to the caller
@@ -300,6 +390,7 @@ class PregelRunner:
return
elif len(tasks) == 1 and get_waiter is None and timeout is None:
t = tasks[0]
scheduled_error_handler = False
try:
await arun_with_retry(
t,
@@ -321,12 +412,22 @@ class PregelRunner:
self.commit(t, None)
except Exception as exc:
self.commit(t, exc)
if (
not isinstance(exc, GraphBubbleUp)
and self._should_route_to_error_handler(t)
and self.aschedule_error_handler is not None
):
self._handled_exception_ids.add(id(exc))
if handler_task := await self.aschedule_error_handler(t, exc):
tasks = (handler_task,)
scheduled_error_handler = True
if reraise and futures:
# will be re-raised after futures are done
fut: asyncio.Future = loop.create_future()
fut.set_exception(exc)
futures.done.add(fut)
elif reraise:
if id(exc) not in self._handled_exception_ids:
# will be re-raised after futures are done
fut: asyncio.Future = loop.create_future()
fut.set_exception(exc)
futures.done.add(fut)
elif reraise and id(exc) not in self._handled_exception_ids:
if tb := exc.__traceback__:
while tb.tb_next is not None and any(
tb.tb_frame.f_code.co_filename.endswith(name)
@@ -335,10 +436,12 @@ class PregelRunner:
tb = tb.tb_next
exc.__traceback__ = tb
raise
if not futures: # maybe `t` scheduled another task
if not futures and not scheduled_error_handler:
# maybe `t` scheduled another task
return
else:
tasks = () # don't reschedule this task
if not scheduled_error_handler:
tasks = () # don't reschedule this task
# add waiter task if requested
if get_waiter is not None:
futures[get_waiter()] = None
@@ -373,6 +476,7 @@ class PregelRunner:
# each task is independent from all other concurrent tasks
# yield updates/debug output as each task finishes
end_time = timeout + loop.time() if timeout else None
handled_futures: set[asyncio.Future[Any]] = set()
while len(futures) > (1 if get_waiter is not None else 0):
done, inflight = await asyncio.wait(
futures,
@@ -381,17 +485,59 @@ class PregelRunner:
)
if not done:
break # timed out
done_for_stop: set[asyncio.Future[Any]] = set()
for fut in done:
task = futures.pop(fut)
if task is None:
# waiter task finished, schedule another
if inflight and get_waiter is not None:
futures[get_waiter()] = None
elif (
(task_exc := _exception(fut))
and self._should_route_to_error_handler(task)
and not isinstance(task_exc, GraphBubbleUp)
):
self._handled_exception_ids.add(id(task_exc))
SKIP_RERAISE_SET.add(fut)
handled_futures.add(fut)
if self.aschedule_error_handler is not None:
if handler_task := await self.aschedule_error_handler(
task, task_exc
):
handler_fut = cast(
asyncio.Future,
self.submit()( # type: ignore[misc]
arun_with_retry,
handler_task,
retry_policy,
stream=self.use_astream,
configurable={
CONFIG_KEY_CALL: partial(
_acall,
weakref.ref(handler_task),
retry_policy=retry_policy,
stream=self.use_astream,
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
loop=loop,
),
},
__name__=handler_task.name,
__cancel_on_exit__=True,
__reraise_on_exit__=reraise,
),
)
futures[handler_fut] = handler_task
else:
done_for_stop.add(fut)
else:
# remove references to loop vars
del fut, task
# maybe stop other tasks
if _should_stop_others(done):
if _should_stop_others(
done_for_stop, handled_exception_ids=self._handled_exception_ids
):
break
# give control back to the caller
yield
@@ -411,6 +557,8 @@ class PregelRunner:
futures.done.union(f for f, t in futures.items() if t is not None),
timeout_exc_cls=asyncio.TimeoutError,
panic=reraise,
handled_exception_ids=self._handled_exception_ids,
handled_futures=handled_futures,
)
except Exception as exc:
if tb := exc.__traceback__:
@@ -446,6 +594,11 @@ class PregelRunner:
else:
# save error to checkpointer
task.writes.append((ERROR, exception))
if self._should_route_to_error_handler(task) and not isinstance(
exception, GraphBubbleUp
):
# Mark early in commit path; loop-side routing may happen later.
self._handled_exception_ids.add(id(exception))
self.put_writes()(task.id, task.writes) # type: ignore[misc]
else:
if self.node_finished and (
@@ -461,6 +614,8 @@ class PregelRunner:
def _should_stop_others(
done: set[F],
*,
handled_exception_ids: set[int] | None = None,
) -> bool:
"""Check if any task failed, if so, cancel all other tasks.
GraphInterrupts are not considered failures."""
@@ -468,7 +623,11 @@ def _should_stop_others(
if fut.cancelled():
continue
elif exc := fut.exception():
if not isinstance(exc, GraphBubbleUp) and fut not in SKIP_RERAISE_SET:
if (
id(exc) not in (handled_exception_ids or set())
and not isinstance(exc, GraphBubbleUp)
and fut not in SKIP_RERAISE_SET
):
return True
return False
@@ -492,6 +651,9 @@ def _panic_or_proceed(
*,
timeout_exc_cls: type[Exception] = TimeoutError,
panic: bool = True,
handled_exception_ids: set[int] | None = None,
handled_futures: Collection[concurrent.futures.Future[Any] | asyncio.Future[Any]]
| None = None,
) -> None:
"""Cancel remaining tasks if any failed, re-raise exception if panic is True."""
done: set[concurrent.futures.Future[Any] | asyncio.Future[Any]] = set()
@@ -508,6 +670,10 @@ def _panic_or_proceed(
# if any task failed
fut = done.pop()
if exc := _exception(fut):
if fut in (handled_futures or set()):
continue
if id(exc) in (handled_exception_ids or set()):
continue
# cancel all pending tasks
while inflight:
inflight.pop().cancel()
@@ -537,6 +703,7 @@ def _call(
*,
retry_policy: Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
timeout: TimeoutPolicy | None = None,
callbacks: Callbacks = None,
futures: weakref.ref[FuturesDict],
schedule_task: Callable[
@@ -560,6 +727,7 @@ def _call(
retry_policy=retry_policy,
cache_policy=cache_policy,
callbacks=callbacks,
timeout=timeout,
),
):
if fut := next(
@@ -624,6 +792,7 @@ def _acall(
*,
retry_policy: Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
timeout: TimeoutPolicy | None = None,
callbacks: Callbacks = None,
# injected dependencies
futures: weakref.ref[FuturesDict],
@@ -657,6 +826,7 @@ def _acall(
input,
retry_policy=retry_policy,
cache_policy=cache_policy,
timeout=timeout,
callbacks=callbacks,
futures=futures,
schedule_task=schedule_task,
@@ -678,6 +848,7 @@ async def _acall_impl(
*,
retry_policy: Sequence[RetryPolicy] | None = None,
cache_policy: CachePolicy | None = None,
timeout: TimeoutPolicy | None = None,
callbacks: Callbacks = None,
# injected dependencies
futures: weakref.ref[FuturesDict[asyncio.Future, asyncio.Event]],
@@ -703,6 +874,7 @@ async def _acall_impl(
retry_policy=retry_policy,
cache_policy=cache_policy,
callbacks=callbacks,
timeout=timeout,
),
):
if fut := next(
+268
View File
@@ -0,0 +1,268 @@
from __future__ import annotations
from collections.abc import AsyncIterator, Callable, Iterator
from contextvars import ContextVar, Token
from typing import Any, TypeVar, cast
from uuid import UUID
from langchain_core.callbacks import BaseCallbackHandler
from langgraph._internal._constants import NS_SEP
from langgraph.constants import TAG_NOSTREAM
from langgraph.pregel.protocol import StreamChunk
try:
from langchain_core.tracers._streaming import _StreamingCallbackHandler
except ImportError:
_StreamingCallbackHandler = object # type: ignore[assignment,misc]
T = TypeVar("T")
ToolCallWriter = Callable[[Any], None]
"""A closure bound to a single tool call that emits `tool-output-delta` events."""
_tool_call_writer: ContextVar[ToolCallWriter | None] = ContextVar(
"langgraph_tool_call_writer", default=None
)
"""ContextVar holding the writer for the currently-executing tool call.
Set by `StreamToolCallHandler.on_tool_start` and reset on end/error.
Read by `ToolRuntime.emit_output_delta` (in `langgraph.prebuilt`).
"""
class StreamToolCallHandler(BaseCallbackHandler, _StreamingCallbackHandler):
"""Callback handler that emits tool-call lifecycle events on the stream.
Fires on LangChain's `on_tool_*` callbacks and pushes to the `tools`
stream mode. Emits `tool-started` / `tool-output-delta` /
`tool-finished` / `tool-error` payloads keyed by `tool_call_id`.
While a tool is executing, this handler sets `_tool_call_writer` to a
closure bound to that call's namespace and `tool_call_id`.
`ToolRuntime.emit_output_delta` reads that ContextVar so tool bodies
can stream partial output without threading the writer through their
own signature.
Attached by `Pregel.stream` / `astream` when `"tools"` is in
`stream_modes`. `run_inline = True` keeps event ordering
deterministic.
"""
run_inline = True
def __init__(
self,
stream: Callable[[StreamChunk], None],
subgraphs: bool,
*,
parent_ns: tuple[str, ...] | None = None,
) -> None:
"""Configure the handler to stream tool-call events.
Args:
stream: Callable that accepts a `StreamChunk` tuple
`(namespace, mode, payload)` and enqueues it.
subgraphs: Whether to emit events from tools called inside
nested subgraphs. When False, only tools at the
handler's own scope (`parent_ns`) emit.
parent_ns: Namespace where the handler was attached.
Mirrors the `StreamMessagesHandler` escape hatch:
tools whose containing namespace equals `parent_ns`
still emit even with `subgraphs=False`, so a node that
explicitly streams a subgraph with `stream_mode="tools"`
sees its own tools.
"""
self.stream = stream
self.subgraphs = subgraphs
self.parent_ns = parent_ns
# run_id → (namespace, tool_call_id, ContextVar token)
# `on_tool_end` does not receive `tool_call_id` in kwargs, so
# we correlate by `run_id` which is present on every callback.
self._run_to_call: dict[
UUID, tuple[tuple[str, ...], str, Token[ToolCallWriter | None]]
] = {}
def _ns_for_emit(
self,
metadata: dict[str, Any] | None,
tags: list[str] | None,
) -> tuple[str, ...] | None:
"""Resolve the namespace this tool call should emit at, or `None` to skip.
Mirrors `StreamMessagesHandler.on_chat_model_start`'s namespace
derivation: parses `langgraph_checkpoint_ns` (which ends with
the `node_name:task_id` of the calling node), drops that
trailing segment, and returns the containing subgraph's own
namespace. Returns `None` when the call should be silently
suppressed:
- `metadata` is missing handler is attached to a context
without Pregel routing info.
- `TAG_NOSTREAM` is in `tags` caller explicitly opted out.
- Tool runs in a subgraph (`len(ns) > 0`) and the handler was
attached with `subgraphs=False` and a different `parent_ns`
than the call's containing subgraph.
"""
if not metadata:
return None
if tags and TAG_NOSTREAM in tags:
return None
nskey = metadata.get("langgraph_checkpoint_ns")
if not nskey:
ns: tuple[str, ...] = ()
else:
ns = tuple(cast(str, nskey).split(NS_SEP))[:-1]
if not self.subgraphs and len(ns) > 0 and ns != self.parent_ns:
return None
return ns
def _start(
self,
serialized: dict[str, Any] | None,
input_str: str,
*,
run_id: UUID,
metadata: dict[str, Any] | None,
tags: list[str] | None,
inputs: dict[str, Any] | None,
kwargs: dict[str, Any],
) -> None:
ns = self._ns_for_emit(metadata, tags)
if ns is None:
return
tool_call_id = cast("str | None", kwargs.get("tool_call_id")) or str(run_id)
tool_name = (
(serialized or {}).get("name")
or cast("str | None", kwargs.get("name"))
or ""
)
def writer(delta: Any) -> None:
self.stream(
(
ns,
"tools",
{
"event": "tool-output-delta",
"tool_call_id": tool_call_id,
"delta": delta,
},
)
)
token = _tool_call_writer.set(writer)
self._run_to_call[run_id] = (ns, tool_call_id, token)
payload: dict[str, Any] = {
"event": "tool-started",
"tool_call_id": tool_call_id,
"tool_name": tool_name,
}
if inputs is not None:
payload["input"] = inputs
self.stream((ns, "tools", payload))
def _end(self, output: Any, *, run_id: UUID) -> None:
info = self._run_to_call.pop(run_id, None)
if info is None:
return
ns, tool_call_id, token = info
self._reset_writer(token)
self.stream(
(
ns,
"tools",
{
"event": "tool-finished",
"tool_call_id": tool_call_id,
"output": output,
},
)
)
def _error(self, error: BaseException, *, run_id: UUID) -> None:
info = self._run_to_call.pop(run_id, None)
if info is None:
return
ns, tool_call_id, token = info
self._reset_writer(token)
self.stream(
(
ns,
"tools",
{
"event": "tool-error",
"tool_call_id": tool_call_id,
"message": str(error),
},
)
)
def tap_output_aiter(
self, run_id: UUID, output: AsyncIterator[T]
) -> AsyncIterator[T]:
"""Pass-through — required by the `_StreamingCallbackHandler` protocol."""
return output
def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
"""Pass-through — sync counterpart to `tap_output_aiter`."""
return output
@staticmethod
def _reset_writer(token: Token[ToolCallWriter | None]) -> None:
# Token is invalid if `on_tool_end` runs in a different context
# than `on_tool_start` (e.g. langchain may hand off to a thread
# worker without copying the context). Swallow that case; the
# ContextVar lifetime is bounded by the enclosing task anyway.
try:
_tool_call_writer.reset(token)
except ValueError:
pass
# ------------------------------------------------------------------
# Sync callbacks
# ------------------------------------------------------------------
def on_tool_start(
self,
serialized: dict[str, Any],
input_str: str,
*,
run_id: UUID,
parent_run_id: UUID | None = None,
tags: list[str] | None = None,
metadata: dict[str, Any] | None = None,
inputs: dict[str, Any] | None = None,
**kwargs: Any,
) -> Any:
self._start(
serialized,
input_str,
run_id=run_id,
metadata=metadata,
tags=tags,
inputs=inputs,
kwargs=kwargs,
)
def on_tool_end(
self,
output: Any,
*,
run_id: UUID,
parent_run_id: UUID | None = None,
**kwargs: Any,
) -> Any:
self._end(output, run_id=run_id)
def on_tool_error(
self,
error: BaseException,
*,
run_id: UUID,
parent_run_id: UUID | None = None,
**kwargs: Any,
) -> Any:
self._error(error, run_id=run_id)
+75 -2
View File
@@ -4,16 +4,27 @@ import ast
import inspect
import re
import textwrap
from collections.abc import Callable
from collections.abc import Callable, Sequence
from functools import partial
from typing import Any
from langchain_core.runnables import Runnable, RunnableLambda, RunnableSequence
from langchain_core.runnables import (
Runnable,
RunnableLambda,
RunnableParallel,
RunnableSequence,
)
from langchain_core.runnables.base import RunnableBindingBase
from langchain_core.runnables.config import run_in_executor
from langgraph.checkpoint.base import ChannelVersions
from typing_extensions import override
from langgraph._internal._runnable import RunnableCallable, RunnableSeq
from langgraph._internal._timeout import sync_timeout_unsupported
from langgraph.pregel.protocol import PregelProtocol
_SEQUENCE_TYPES = (RunnableSeq, RunnableSequence)
def get_new_channel_versions(
previous_versions: ChannelVersions, current_versions: ChannelVersions
@@ -64,6 +75,68 @@ def find_subgraph_pregel(candidate: Runnable) -> PregelProtocol | None:
return None
def _sequence_steps(runnable: Runnable) -> Sequence[Runnable] | None:
if isinstance(runnable, _SEQUENCE_TYPES):
return runnable.steps
return None
def _parallel_steps(runnable: Runnable) -> Sequence[Runnable] | None:
if isinstance(runnable, RunnableParallel):
return tuple(runnable.steps__.values())
return None
def _has_method_override(runnable: Runnable, method_name: str) -> bool:
method = getattr(type(runnable), method_name, None)
return method is not None and method is not getattr(Runnable, method_name)
def _is_executor_backed_afunc(afunc: Callable[..., Any] | None) -> bool:
return isinstance(afunc, partial) and afunc.func is run_in_executor
def _has_native_async(runnable: Runnable) -> bool:
if isinstance(runnable, RunnableCallable):
return runnable.afunc is not None and not _is_executor_backed_afunc(
runnable.afunc
)
if isinstance(runnable, RunnableLambda):
return bool(getattr(runnable, "afunc", False))
return _has_method_override(runnable, "ainvoke")
def _runnable_has_native_async(runnable: Runnable) -> bool:
"""Return whether a runnable can be idle-timed without known sync code.
For custom runnable subclasses, an `ainvoke` override is treated as the
async contract. We do not introspect whether that implementation delegates
to blocking work internally e.g. a subclass whose `ainvoke` calls
`asyncio.to_thread(self.invoke, ...)` will pass this check but the wrapped
sync work is still uncancellable. Idle-timeout enforcement on such a
runnable will fire `NodeTimeoutError` correctly, but the background thread
will keep running until its sync work returns.
"""
while isinstance(runnable, RunnableBindingBase):
runnable = runnable.bound
steps = _sequence_steps(runnable)
if steps is None:
steps = _parallel_steps(runnable)
if steps is not None:
return all(_runnable_has_native_async(step) for step in steps)
# Raw callables and the common composition wrappers created by graph
# builders fall through here. We do not exhaustively unwrap every Runnable
# wrapper — wrappers that provide `ainvoke` are treated as owning the async
# contract.
return _has_native_async(runnable)
def validate_timeout_supported(runnable: Runnable, *, name: str) -> None:
if not _runnable_has_native_async(runnable):
raise sync_timeout_unsupported(name)
def get_function_nonlocals(func: Callable) -> list[Any]:
"""Get the nonlocal variables accessed by a function.
File diff suppressed because it is too large Load Diff
+68 -2
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, field, replace
from typing import Any, Generic, cast
@@ -15,6 +16,7 @@ from langgraph.typing import ContextT
__all__ = (
"BaseUser",
"ExecutionInfo",
"RunControl",
"Runtime",
"ServerInfo",
"get_runtime",
@@ -74,16 +76,49 @@ class ServerInfo:
"""
class RunControl:
"""Run-scoped control surface for cooperative draining.
Intended for a single graph run. Create a fresh `RunControl` per run;
reusing a control after `request_drain()` leaves it drained.
Safe to call from any thread: the drain request is represented by a
single attribute write, so no lock is needed for this signal.
If more mutable state is added here, add synchronization.
"""
__slots__ = ("_drain_reason",)
def __init__(self) -> None:
self._drain_reason: str | None = None
def request_drain(self, reason: str = "shutdown") -> None:
self._drain_reason = reason
@property
def drain_requested(self) -> bool:
return self._drain_reason is not None
@property
def drain_reason(self) -> str | None:
return self._drain_reason
def _no_op_stream_writer(_: Any) -> None: ...
def _no_op_heartbeat() -> None: ...
class _RuntimeOverrides(TypedDict, Generic[ContextT], total=False):
context: ContextT
store: BaseStore | None
stream_writer: StreamWriter
heartbeat: Callable[[], None]
previous: Any
execution_info: ExecutionInfo
server_info: ServerInfo | None
control: RunControl | None
@dataclass(**_DC_KWARGS)
@@ -162,7 +197,7 @@ class Runtime(Generic[ContextT]):
context: ContextT = field(default=None) # type: ignore[assignment]
"""Static context for the graph run, like `user_id`, `db_conn`, etc.
Can also be thought of as 'run dependencies'."""
store: BaseStore | None = field(default=None)
@@ -171,9 +206,19 @@ class Runtime(Generic[ContextT]):
stream_writer: StreamWriter = field(default=_no_op_stream_writer)
"""Function that writes to the custom stream."""
heartbeat: Callable[[], None] = field(default=_no_op_heartbeat)
"""Record progress for the current node's `idle_timeout`.
Call this from inside long-running work that does not naturally emit
writes, stream chunks, child tasks, or LangChain callback events, to
prevent the node from being treated as idle. It is also the only
progress signal honored under `TimeoutPolicy(refresh_on="heartbeat")`.
Outside an idle-timed attempt this is a no-op.
"""
previous: Any = field(default=None)
"""The previous return value for the given thread.
Only available with the functional API when a checkpointer is provided.
"""
@@ -185,6 +230,13 @@ class Runtime(Generic[ContextT]):
server_info: ServerInfo | None = field(default=None)
"""Metadata injected by LangGraph Server. None when running open-source LangGraph without LangSmith deployments."""
control: RunControl | None = field(default=None)
"""Run-scoped control plane for cooperative draining.
Populated automatically during graph runs. None outside an active
graph runtime.
"""
def merge(self, other: Runtime[ContextT]) -> Runtime[ContextT]:
"""Merge two runtimes together.
@@ -196,9 +248,13 @@ class Runtime(Generic[ContextT]):
stream_writer=other.stream_writer
if other.stream_writer is not _no_op_stream_writer
else self.stream_writer,
heartbeat=other.heartbeat
if other.heartbeat is not _no_op_heartbeat
else self.heartbeat,
previous=self.previous if other.previous is None else other.previous,
execution_info=other.execution_info or self.execution_info,
server_info=other.server_info or self.server_info,
control=other.control or self.control,
)
def override(
@@ -217,13 +273,23 @@ class Runtime(Generic[ContextT]):
execution_info=self.execution_info.patch(**overrides),
)
@property
def drain_requested(self) -> bool:
return self.control.drain_requested if self.control is not None else False
@property
def drain_reason(self) -> str | None:
return self.control.drain_reason if self.control is not None else None
DEFAULT_RUNTIME = Runtime(
context=None,
store=None,
stream_writer=_no_op_stream_writer,
heartbeat=_no_op_heartbeat,
previous=None,
execution_info=None,
control=None,
)
@@ -0,0 +1,45 @@
"""Streaming infrastructure for LangGraph.
Compile a graph with `transformers=[...]` and call `graph.stream_events(version="v3")` /
`graph.astream_events(version="v3")` to drive a transformer pipeline that projects the
graph's raw events into ergonomic per-channel streams.
"""
from langgraph.stream._types import ProtocolEvent, StreamTransformer
from langgraph.stream.run_stream import (
AsyncGraphRunStream,
AsyncSubgraphRunStream,
GraphRunStream,
SubgraphRunStream,
)
from langgraph.stream.stream_channel import StreamChannel
from langgraph.stream.transformers import (
CheckpointsTransformer,
CustomTransformer,
DebugTransformer,
LifecyclePayload,
LifecycleTransformer,
SubgraphStatus,
SubgraphTransformer,
TasksTransformer,
UpdatesTransformer,
)
__all__ = [
"AsyncGraphRunStream",
"AsyncSubgraphRunStream",
"CheckpointsTransformer",
"CustomTransformer",
"DebugTransformer",
"GraphRunStream",
"LifecyclePayload",
"LifecycleTransformer",
"ProtocolEvent",
"StreamChannel",
"StreamTransformer",
"SubgraphRunStream",
"SubgraphStatus",
"SubgraphTransformer",
"TasksTransformer",
"UpdatesTransformer",
]
@@ -0,0 +1,32 @@
from __future__ import annotations
import time
from typing import Any, cast
from langgraph.stream._types import ProtocolEvent, _ProtocolEventParams
from langgraph.types import StreamPart
def convert_to_protocol_event(part: StreamPart) -> ProtocolEvent:
"""Convert a v2 StreamPart to a ProtocolEvent.
Args:
part: A stream part with keys `type`, `ns`, `data`, and
optionally `interrupts` (present on values events).
Returns:
The equivalent ProtocolEvent.
"""
part_dict = cast(dict[str, Any], part)
params: _ProtocolEventParams = {
"namespace": list(part_dict["ns"]),
"timestamp": int(time.time() * 1000),
"data": part_dict["data"],
}
if "interrupts" in part_dict:
params["interrupts"] = part_dict["interrupts"]
return {
"type": "event",
"method": part_dict["type"],
"params": params,
}
+498
View File
@@ -0,0 +1,498 @@
from __future__ import annotations
import asyncio
import time
from collections.abc import Awaitable, Callable
from typing import Any
from langgraph.stream._types import (
ProtocolEvent,
StreamTransformer,
transformer_requires_async,
)
from langgraph.stream.stream_channel import StreamChannel
TransformerFactory = Callable[["tuple[str, ...]"], StreamTransformer]
"""Factory that builds a scoped transformer for a mux.
Called once per `StreamMux` with the mux's scope (typically `()` for
the root). Standard transformer classes accept a single positional
scope argument, so the class itself is a valid factory. User
transformers can close over their config:
`lambda scope: MyTransformer(scope, foo=...)`.
"""
class StreamMux:
"""Central event dispatcher for the streaming infrastructure.
Owns the main event log and routes events through a transformer
pipeline. StreamChannels with a name discovered in transformer
projections are auto-wired so that every `push()` also injects a
`ProtocolEvent` into the main log. StreamChannels without a name
are local-only.
Pass `is_async=True` when the mux will be consumed via async
iteration (`handler.astream()`). All StreamChannel instances
discovered during registration are automatically bound to the
matching mode.
Attributes:
extensions: Merged projection dict across all registered
transformers. Treat as read-only mutations won't be
reflected back in individual transformers' state.
native_keys: Projection keys contributed by transformers with
`_native = True`.
"""
def __init__(
self,
transformers: list[StreamTransformer] | None = None,
*,
is_async: bool = False,
factories: list[TransformerFactory] | None = None,
scope: tuple[str, ...] = (),
_assign_seq: bool = True,
) -> None:
"""Initialize the mux and register transformers in order.
Callers pass either `transformers` (pre-built instances) or
`factories` (callables producing fresh instances per mux). Each
transformer's `init()` is called, projections are merged into
`extensions`, `_native` keys are recorded in `native_keys`, and
any StreamChannel instances are bound and (if named) wired.
Args:
transformers: Already-built transformer instances. Registered
only on this mux they are NOT cloned into child
mini-muxes built by `_make_child`. Use `factories` for
transformers that should propagate to nested scopes.
is_async: True for async dispatch (`apush` / `aclose` /
`afail`), False for the sync path.
factories: One-argument callables `(scope) -> StreamTransformer`.
Called once with this mux's `scope` here, and cloned
again per child scope by `_make_child` so each
sub-mux gets fresh instances.
scope: The namespace the mux operates within. The root mux
is `()`.
_assign_seq: Internal flag for child muxes. Root muxes assign
monotonic `seq` numbers when appending to their main event
log; child muxes share forwarded event objects and must not
mutate their envelopes.
Raises:
RuntimeError: If any transformer requires an async run but
the mux is in sync mode.
TypeError: If a transformer's `init()` doesn't return a dict.
ValueError: If transformers' projection keys collide.
"""
self.is_async = is_async
self.scope: tuple[str, ...] = scope
self._assign_seq = _assign_seq
self._events: StreamChannel[ProtocolEvent] = StreamChannel()
self._events._bind(is_async=is_async)
self._events._bind_mux(self)
self._transformers: list[StreamTransformer] = []
self._channels: list[StreamChannel[Any]] = []
self._seq = 0
self._push_seq = 0
self.extensions: dict[str, Any] = {}
self.native_keys: set[str] = set()
self._projection_owners: dict[str, str] = {}
self._transformer_by_key: dict[str, StreamTransformer] = {}
# Stored only when constructed from factories — used by
# `_make_child` to clone the transformer pipeline at a deeper
# scope. Pre-built transformers can't be cloned, so a mux
# built with `transformers=` rejects child construction.
self._factories: list[TransformerFactory] | None = (
list(factories) if factories is not None else None
)
self._pump_fn: Callable[[], bool] | None = None
self._apump_fn: Callable[[], Awaitable[bool]] | None = None
# Factories run first (they propagate to child mini-muxes
# via `_make_child`), then any pre-built `transformers=`
# instances are registered as root-only — they aren't cloned
# for child scopes.
if factories is not None:
for factory in factories:
self._register(factory(scope))
for transformer in transformers or ():
self._register(transformer)
def transformer_by_key(self, key: str) -> StreamTransformer | None:
"""Return the transformer that contributed `key` to the projection."""
return self._transformer_by_key.get(key)
def _next_push_seq(self) -> int:
self._push_seq += 1
return self._push_seq
# ------------------------------------------------------------------
# Pump wiring + mini-mux nesting
# ------------------------------------------------------------------
def bind_pump(self, fn: Callable[[], bool]) -> None:
"""Wire the sync pull callback onto every projection in this mux.
Records the pump on the mux so child mini-muxes built by
`_make_child` can inherit it. Propagates to:
- the main event log (`self._events`)
- every projection StreamChannel in `extensions`
- any registered transformer that exposes `_bind_pump` (e.g.
`MessagesTransformer` so `ChatModelStream` instances drive the
shared pump from their cursors)
"""
self._pump_fn = fn
self._events._request_more = fn
for ch in self._channels:
ch._request_more = fn
for transformer in self._transformers:
bind = getattr(transformer, "_bind_pump", None)
if bind is not None:
bind(fn)
def bind_apump(self, fn: Callable[[], Awaitable[bool]]) -> None:
"""Async counterpart to `bind_pump`."""
self._apump_fn = fn
self._events._arequest_more = fn
for ch in self._channels:
ch._arequest_more = fn
for transformer in self._transformers:
abind = getattr(transformer, "_bind_apump", None)
if abind is not None:
abind(fn)
def _make_child(self, scope: tuple[str, ...]) -> StreamMux:
"""Build a mini-mux with the same factories scoped to `scope`.
Used by `SubgraphTransformer` to attach a fresh transformer
pipeline to each discovered subgraph handle. The child mux
inherits the current pump bindings (so cursors on its
projection logs drive the root pump), carries the same factory
list forward to any grandchild subgraphs, and does not assign
`seq` numbers so forwarded events can be shared without
mutating their envelope.
Raises:
RuntimeError: If the mux was not constructed with
`factories=`. Mini-muxes require factories so each scope
gets its own fresh transformer instances.
"""
if self._factories is None:
raise RuntimeError(
"StreamMux._make_child requires the mux to be constructed "
"with `factories=`; pre-built transformers can't be "
"cloned to a new scope."
)
child = StreamMux(
factories=self._factories,
is_async=self.is_async,
scope=scope,
_assign_seq=False,
)
if self._pump_fn is not None:
child.bind_pump(self._pump_fn)
if self._apump_fn is not None:
child.bind_apump(self._apump_fn)
return child
def _register(self, transformer: StreamTransformer) -> None:
"""Register a single transformer.
Calls `transformer.init()`, stores the transformer for event
processing, binds any StreamChannel instances in the projection,
and merges the projection into `extensions`.
"""
if transformer_requires_async(transformer) and not self.is_async:
raise RuntimeError(
f"{type(transformer).__name__} requires an async run — "
"it overrides aprocess/afinalize/afail or sets "
"requires_async=True. Use astream(), not stream()."
)
projection = transformer.init()
if not isinstance(projection, dict):
raise TypeError(
f"StreamTransformer.init() must return a dict, "
f"got {type(projection).__name__}"
)
conflicts = set(projection) & set(self.extensions)
if conflicts:
attributions = ", ".join(
f"{key!r} (owned by {self._projection_owners[key]})"
for key in sorted(conflicts)
)
raise ValueError(
f"Transformer {type(transformer).__name__} returned "
f"projection keys that conflict with already-registered "
f"keys: {attributions}"
)
is_native = bool(getattr(transformer, "_native", False))
self._transformers.append(transformer)
self._bind_and_wire(projection, native=is_native)
self.extensions.update(projection)
owner_name = type(transformer).__name__
for key in projection:
self._projection_owners[key] = owner_name
self._transformer_by_key[key] = transformer
if is_native:
self.native_keys.update(projection.keys())
transformer._on_register(self)
def push(self, event: ProtocolEvent) -> None:
"""Route an event through all transformers, then append to the main log.
Each transformer's `process()` is called in registration order.
If any transformer returns False, the event is suppressed from
the main log, but transformers that already saw it keep their
side effects.
On the root mux, `seq` is assigned right before an event enters
the main log, not before the transformer pipeline runs. This
ensures that events auto-forwarded from StreamChannels during
`process()` get earlier seq numbers than the original event,
preserving monotonic ordering in the root log. Child muxes do
not assign `seq`, so subgraph forwarding can share event objects
without mutating their envelopes.
Args:
event: The protocol event to dispatch.
"""
keep = True
for transformer in self._transformers:
if not transformer.process(event):
keep = False
if keep:
if self._assign_seq:
self._seq += 1
event["seq"] = self._seq
self._events.push(event)
def close(self) -> None:
"""Finalize all transformers, close all projections and the main log.
StreamChannels discovered in transformer projections are
auto-closed after `finalize()` runs transformers don't need
to close them manually. If any transformer's `finalize()` raises,
the remaining transformers, projections, and the main log are
still closed; the first error is re-raised after cleanup
completes.
Raises:
BaseException: The first error raised by a transformer's
`finalize()`, re-raised after cleanup finishes.
"""
first_error: BaseException | None = None
for transformer in self._transformers:
try:
transformer.finalize()
except BaseException as e:
if first_error is None:
first_error = e
for ch in self._channels:
if not ch._closed:
ch.close()
self._events.close()
if first_error is not None:
raise first_error
def fail(self, err: BaseException) -> None:
"""Fail all transformers, projections, and the main log.
StreamChannels discovered in transformer projections are
auto-failed transformers don't need to fail them manually.
If any transformer's `fail()` raises, the remaining
transformers, projections, and the main log are still failed.
Args:
err: The exception that ended the run.
"""
for transformer in self._transformers:
try:
transformer.fail(err)
except BaseException:
pass
for ch in self._channels:
if not ch._closed:
ch.fail(err)
self._events.fail(err)
# ------------------------------------------------------------------
# Async dispatch
# ------------------------------------------------------------------
async def apush(self, event: ProtocolEvent) -> None:
"""Dispatch an event on the async lane.
Awaits each transformer's `aprocess` in registration order
before appending to the main log. A slow `aprocess` serializes
the pipeline by design that's the guarantee that lets a later
transformer (or a synchronous consumer) see the result of the
async work. For decoupled work, use `schedule()` from inside
`process` / `aprocess` instead.
The main log append is a non-blocking `push` matching v1's
`put_nowait` shape. The root mux assigns `seq`; child muxes do
not, so forwarded subgraph events can be shared without copying.
Memory is bounded by caller pace via the caller-driven pump; see
`StreamChannel` for the full tradeoff story.
Args:
event: The protocol event to dispatch.
"""
keep = True
for transformer in self._transformers:
if not await transformer.aprocess(event):
keep = False
if keep:
if self._assign_seq:
self._seq += 1
event["seq"] = self._seq
self._events.push(event)
async def aclose(self) -> None:
"""Finalize on the async lane.
Awaits every task started via `StreamTransformer.schedule()`
across all transformers, then calls `afinalize()` on each,
then auto-closes channels and the main event log.
If any scheduled task raised under `on_error="raise"`, or any
transformer's `afinalize` raises, the exception propagates.
The caller (the pump) handles it by routing into `afail`.
Raises:
BaseException: The first scheduled-task or `afinalize`
error, re-raised after cleanup.
"""
pending = self._collect_scheduled_tasks()
if pending:
results = await asyncio.gather(*pending, return_exceptions=True)
first_err = next(
(
r
for r in results
if isinstance(r, BaseException)
and not isinstance(r, asyncio.CancelledError)
),
None,
)
if first_err is not None:
raise first_err
first_error: BaseException | None = None
for transformer in self._transformers:
try:
await transformer.afinalize()
except BaseException as e:
if first_error is None:
first_error = e
for ch in self._channels:
if not ch._closed:
ch.close()
self._events.close()
if first_error is not None:
raise first_error
async def afail(self, err: BaseException) -> None:
"""Fail on the async lane.
Cancels every scheduled task across all transformers, awaits
them to completion, then runs each transformer's `afail` hook
and auto-fails channels and the main event log.
Args:
err: The exception that ended the run.
"""
pending = self._collect_scheduled_tasks()
for task in pending:
task.cancel()
if pending:
await asyncio.gather(*pending, return_exceptions=True)
for transformer in self._transformers:
try:
await transformer.afail(err)
except BaseException:
pass
for ch in self._channels:
if not ch._closed:
ch.fail(err)
if not self._events._closed:
self._events.fail(err)
def _collect_scheduled_tasks(self) -> list[asyncio.Task[Any]]:
"""Return a snapshot of in-flight tasks scheduled via transformers."""
return [
task
for transformer in self._transformers
for task in getattr(transformer, "_stream_scheduled_tasks", ())
if not task.done()
]
# ------------------------------------------------------------------
# Binding and StreamChannel auto-wiring
# ------------------------------------------------------------------
def _bind_and_wire(
self, projection: dict[str, Any], *, native: bool = False
) -> None:
"""Bind and optionally wire StreamChannel instances in a projection.
All StreamChannels are bound and tracked. Channels with a name
are additionally wired for protocol auto-forwarding.
Args:
projection: The projection dict returned by a transformer's
`init()`.
native: True when the owning transformer is `_native`.
Named channels owned by a native transformer use the
channel name directly as the protocol method;
user-defined channels are prefixed with `custom:`.
"""
for value in projection.values():
if isinstance(value, StreamChannel):
value._bind(is_async=self.is_async)
value._bind_mux(self)
self._channels.append(value)
if value.name is not None:
method = value.name if native else f"custom:{value.name}"
def _make_forward(method_name: str) -> Callable[[Any], None]:
def _forward(item: Any) -> None:
self._forward(method_name, item)
return _forward
value._wire(_make_forward(method))
def _forward(self, method: str, item: Any) -> None:
"""Inject a ProtocolEvent for a StreamChannel push.
Forwarded events bypass the transformer pipeline to avoid
infinite recursion (a transformer that pushes to a channel
during `process()` would re-trigger itself). These events are
visible in this mux's main event log but are not passed through
transformers' `process()` methods. Only the root mux assigns
`seq` to forwarded channel events.
Args:
method: The full protocol method (already with or without
the `custom:` prefix; resolved by `_bind_and_wire`).
item: The payload pushed onto the channel.
"""
event: ProtocolEvent = {
"type": "event",
"method": method,
"params": {
"namespace": [],
"timestamp": int(time.time() * 1000),
"data": item,
},
}
if self._assign_seq:
self._seq += 1
event["seq"] = self._seq
self._events.push(event)
+313
View File
@@ -0,0 +1,313 @@
from __future__ import annotations
import asyncio
import logging
from abc import ABC, abstractmethod
from collections.abc import Coroutine
from typing import Any, ClassVar, Literal
from typing_extensions import NotRequired, TypedDict
_logger = logging.getLogger(__name__)
class _ProtocolEventParams(TypedDict):
"""Parameters for a protocol event.
`timestamp` is wall-clock milliseconds since the epoch and can go
backwards across NTP adjustments use `ProtocolEvent.seq` for
ordering.
"""
namespace: list[str]
timestamp: int
data: Any
interrupts: NotRequired[tuple[Any, ...]]
class ProtocolEvent(TypedDict):
"""A protocol event emitted by the streaming infrastructure.
Wraps a raw stream part (values, messages, custom, etc.) in a uniform
envelope with a monotonic sequence number assigned by the root StreamMux.
Consumers that need a total order across root events should use `seq`, not
`params.timestamp` (which is wall-clock and not monotonic).
"""
type: Literal["event"]
eventId: NotRequired[str]
seq: NotRequired[int]
method: str # StreamMode value: "values", "messages", "custom", etc.
params: _ProtocolEventParams
class StreamTransformer(ABC):
"""Extension point for custom stream projections.
Transformers observe protocol events flowing through the StreamMux and
build typed derived projections (StreamChannels, promises, etc.).
Set `_native = True` on a transformer to have its projection keys
exposed as direct attributes on the run stream (in addition to
appearing in `run.extensions`).
Subclasses must implement `init` and override at least one of
`process` / `aprocess`. The `finalize` / `afinalize` and `fail` /
`afail` hooks are optional the default implementations are no-ops.
StreamChannel instances in the projection dict are auto-closed /
auto-failed by the mux, so most transformers don't need `finalize`
or `fail` at all.
Transformers that need async work pick the async lane by:
1. Overriding `aprocess` (and optionally `afinalize` / `afail`), or
2. Calling `self.schedule(coro)` from inside a sync `process`, or
3. Setting `requires_async = True` explicitly.
The mux detects these cases at registration and raises if they're
used under sync `stream()` they only work under `astream()`.
Use `aprocess` when the pump must wait for async work before the
next transformer sees the event (e.g. PII redaction that mutates
`event` in place). Use `schedule()` for decoupled async work whose
result lands on an independent projection (e.g. async moderation
scoring, cost lookup, external tracing).
Attributes:
scope: Namespace the transformer operates within `()` for the
root mux. Set at construction from the mux's scope (each
factory is called as `factory(scope)`).
requires_async: Explicit opt-in for transformers that need a
running event loop but don't override any async method (for
example, transformers that call `schedule()` from a sync
`process`). The mux also auto-detects the async lane when
`aprocess`, `afinalize`, or `afail` is overridden.
supports_sync: Set True only for transformers that override
async-lane hooks while still fully supporting the sync lane.
Such transformers may be registered under `stream()`.
required_stream_modes: Stream modes the graph must emit for
this transformer to have anything to process. Computed as
the union across all registered transformers to determine
which modes a `stream_events(version="v3")` run requests from the graph.
Empty tuple means the transformer consumes only synthetic
events (or is purely passive).
"""
requires_async: ClassVar[bool] = False
supports_sync: ClassVar[bool] = False
required_stream_modes: ClassVar[tuple[str, ...]] = ()
def __init__(self, scope: tuple[str, ...] = ()) -> None:
"""Initialize the transformer with its mux's scope.
Args:
scope: The namespace tuple the owning mux is scoped to.
`()` for the root. Factories receive this at
construction time (`factory(scope)` in `StreamMux`).
"""
self.scope: tuple[str, ...] = scope
@abstractmethod
def init(self) -> dict[str, Any]:
"""Return the projection dict.
Keys become entries in `run.extensions`. If the transformer has
`_native = True`, keys are also set as direct attributes on the
run stream.
StreamChannel instances in the return value are automatically
wired by the StreamMux for protocol event auto-forwarding.
"""
...
def _on_register(self, mux: Any) -> None:
"""Called by `StreamMux._register` after this transformer is wired in.
Default is a no-op. Override to capture a reference to the
owning mux needed for transformers that build mini-muxes
via `mux._make_child(...)` (e.g. `SubgraphTransformer`).
"""
def process(self, event: ProtocolEvent) -> bool:
"""Handle an event on the sync lane.
Called for every event before it is appended to the main event
log. Subclasses must override either `process` or `aprocess`.
The default raises so a missing override fails loudly rather
than silently passing every event through.
Args:
event: The protocol event to observe.
Returns:
True to keep the event in the main log, False to suppress it.
"""
raise NotImplementedError(
f"{type(self).__name__} must override process() or aprocess()"
)
async def aprocess(self, event: ProtocolEvent) -> bool:
"""Handle an event on the async lane.
The mux awaits this before dispatching to the next transformer,
so a slow `aprocess` serializes the pipeline. Use it only when
a later transformer or a consumer reading the event
synchronously must see the result of the async work (e.g.
PII redaction that mutates `event` in place).
The default delegates to `process`, so purely-sync transformers
run unchanged under `astream()`.
Args:
event: The protocol event to observe.
Returns:
True to keep the event in the main log, False to suppress it.
"""
return self.process(event)
def finalize(self) -> None:
"""Called when the run ends normally (sync lane).
Override to close StreamChannels, resolve promises, or perform
other teardown. StreamChannel instances in the projection dict
are auto-closed by the mux.
"""
async def afinalize(self) -> None:
"""Called when the run ends normally (async lane).
By the time this runs, the mux has already awaited every task
started via `schedule()`, so StreamChannels can be closed here
without a last-task-wins race.
The default delegates to `finalize`.
"""
self.finalize()
def fail(self, err: BaseException) -> None:
"""Called when the run ends with an error (sync lane).
Override to fail StreamChannels, reject promises, or perform
other teardown. StreamChannel instances in the projection dict
are auto-failed by the mux.
Args:
err: The exception that ended the run.
"""
async def afail(self, err: BaseException) -> None:
"""Called when the run ends with an error (async lane).
The mux cancels and awaits every task started via `schedule()`
before calling this, so cleanup doesn't race with in-flight work.
The default delegates to `fail`.
Args:
err: The exception that ended the run.
"""
self.fail(err)
# ------------------------------------------------------------------
# Scheduled async work
# ------------------------------------------------------------------
def schedule(
self,
coro: Coroutine[Any, Any, Any],
*,
on_error: Literal["log", "raise"] = "log",
) -> asyncio.Task[Any]:
"""Schedule a coroutine tied to this transformer's lifecycle.
The mux holds the task reference, awaits all scheduled tasks
during `aclose()` before calling `afinalize()`, and cancels
them on `afail()`. Authors don't need to track tasks or
implement the last-task-closes-the-log dance.
Requires a running event loop call only under `astream()`.
Set `requires_async = True` on the class so registration under
sync `stream()` fails fast with a clear message.
Args:
coro: The coroutine to run. Its lifecycle is owned by the
mux from this point on.
on_error: `"log"` (default) catches and logs any exception
the coroutine raises, so a single failure doesn't tear
down the run. `"raise"` lets the exception propagate
when the mux joins pendings, converting the close path
into the fail path.
Returns:
The asyncio Task. Authors rarely need to await it directly
consumers read results from whatever projection the
coroutine pushes into.
Raises:
RuntimeError: If called without a running event loop (i.e.
under sync `stream()` rather than `astream()`).
"""
try:
asyncio.get_running_loop()
except RuntimeError:
raise RuntimeError(
f"{type(self).__name__}.schedule() requires a running "
"event loop; this transformer must run under astream(), "
"not stream(). Set requires_async=True on the class so "
"this fails at registration rather than at first event."
) from None
wrapped = self._wrap_scheduled(coro) if on_error == "log" else coro
task = asyncio.create_task(wrapped)
tasks = self._scheduled_task_set()
tasks.add(task)
task.add_done_callback(tasks.discard)
return task
@staticmethod
async def _wrap_scheduled(coro: Coroutine[Any, Any, Any]) -> Any:
try:
return await coro
except asyncio.CancelledError:
raise
except BaseException:
_logger.exception("Scheduled StreamTransformer task failed")
def _scheduled_task_set(self) -> set[asyncio.Task[Any]]:
"""Return the lazily-allocated task set.
Avoids requiring subclasses to call `super().__init__()`.
"""
tasks: set[asyncio.Task[Any]] | None = getattr(
self, "_stream_scheduled_tasks", None
)
if tasks is None:
tasks = set()
self._stream_scheduled_tasks = tasks
return tasks
def transformer_requires_async(transformer: StreamTransformer) -> bool:
"""Return True if the transformer needs a running event loop.
A transformer requires async if it explicitly opts in
(`requires_async = True`) or overrides any of the async-lane methods
(`aprocess`, `afinalize`, `afail`) without also declaring that it
supports the sync lane.
Args:
transformer: The transformer to inspect.
Returns:
True if the transformer cannot run under sync `stream()`.
"""
if transformer.requires_async:
return True
if transformer.supports_sync:
return False
cls = type(transformer)
for name in ("aprocess", "afinalize", "afail"):
if getattr(cls, name) is not getattr(StreamTransformer, name):
return True
return False
@@ -0,0 +1,608 @@
from __future__ import annotations
import asyncio
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
from types import MappingProxyType, TracebackType
from typing import TYPE_CHECKING, Any
from langchain_core._api import beta
from langgraph.stream._convert import convert_to_protocol_event
from langgraph.stream._mux import StreamMux
from langgraph.stream._types import ProtocolEvent
if TYPE_CHECKING:
from langgraph.stream.transformers import SubgraphStatus
def _drive_until_done(pump: Callable[[], bool]) -> None:
"""Call the sync pump until it returns False."""
while pump():
pass
async def _adrive_until_done(pump: Callable[[], Awaitable[bool]]) -> None:
"""Call the async pump until it returns False."""
while await pump():
pass
@beta(message="The v3 streaming protocol on Pregel is experimental.")
class GraphRunStream:
"""Sync run stream with caller-driven pumping.
The caller's iteration on any projection (`values`, `messages`,
raw events, or `output`) drives the graph forward. No background
thread is used the caller's `for` loop is the pump.
Projections are single-consumer iterating `run.values` twice
raises. Use `projection.tee(n)` if you genuinely need fan-out.
All transformer projections live in `extensions`. Native transformer
projections (those with `_native = True`) are also set as direct
attributes on this instance (e.g. `run.values`, `run.messages`).
!!! warning
Returned by `Pregel.stream_events(version="v3")`, which is
experimental and may change.
"""
def __init__(
self,
graph_iter: Iterator[Any] | None,
mux: StreamMux,
*,
wire_pump: bool = True,
) -> None:
"""Initialize the run stream.
Args:
graph_iter: Pull-based iterator over the graph's stream,
or `None` for nested run streams whose pump is driven
by an outer run (e.g. `SubgraphRunStream`).
mux: The StreamMux owning projections and the main log.
wire_pump: When True (default), bind `_pump_next` as the
mux's pump callable. Subclasses that inherit a parent
pump via `StreamMux._make_child` should pass False to
preserve the parent binding.
"""
self._graph_iter = graph_iter
self._mux = mux
self.extensions: Mapping[str, Any] = MappingProxyType(mux.extensions)
self._exhausted = False
self._latest: dict[str, Any] | None = None
self._interrupted = False
self._interrupts: list[Any] = []
self._scope_list: list[str] = list(mux.scope)
for key in mux.native_keys:
setattr(self, key, mux.extensions[key])
if wire_pump:
self._wire_request_more(mux)
def _wire_request_more(self, mux: StreamMux) -> None:
"""Wire the sync pull callback through the mux.
Routing through `mux.bind_pump` (rather than walking
projections directly here) lets child mini-muxes built by
`mux._make_child(...)` inherit the same pump callable, so
cursors on a subgraph handle's projections drive the root
pump just like cursors on `run.values` do.
"""
mux.bind_pump(self._pump_next)
def _observe_event(self, event: ProtocolEvent) -> None:
"""Track values-event state for output/interrupted/interrupts."""
if event["method"] != "values":
return
params = event["params"]
if params["namespace"] != self._scope_list:
return
self._latest = params["data"]
interrupts = params.get("interrupts", ())
if interrupts:
self._interrupted = True
self._interrupts.extend(interrupts)
def _pump_next(self) -> bool:
"""Pull one event from the graph and push it through the mux.
Returns:
True if an event was pulled, False if the graph is exhausted
or has raised. Always False when constructed with
`graph_iter=None` (the run is driven by an outer pump).
"""
if self._exhausted or self._graph_iter is None:
return False
try:
part = next(self._graph_iter)
event = convert_to_protocol_event(part)
self._observe_event(event)
self._mux.push(event)
return True
except StopIteration:
self._mux.close()
self._exhausted = True
return False
except Exception as e:
self._mux.fail(e)
self._exhausted = True
return False
def abort(self) -> None:
"""Stop the run early.
Closes the mux and marks the stream exhausted. The graph
iterator is dropped; any in-flight nodes see the closure on
their next yield point. Idempotent.
"""
if self._exhausted:
return
self._exhausted = True
try:
self._mux.close()
except Exception:
pass
def __enter__(self) -> GraphRunStream:
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self.abort()
@property
def output(self) -> dict[str, Any] | None:
"""Drive the run to completion and return the final state."""
_drive_until_done(self._pump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._latest
@property
def interrupted(self) -> bool:
"""Drive the run to completion, then return whether it was
interrupted.
Raises:
BaseException: If the run ended with an error.
"""
_drive_until_done(self._pump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._interrupted
@property
def interrupts(self) -> list[Any]:
"""Drive the run to completion, then return interrupt payloads.
Raises:
BaseException: If the run ended with an error.
"""
_drive_until_done(self._pump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._interrupts
def __iter__(self) -> Iterator[ProtocolEvent]:
"""Subscribe to the main event log and iterate protocol events."""
return iter(self._mux._events)
def interleave(self, *names: str) -> Iterator[tuple[str, Any]]:
"""Iterate multiple projections in arrival order, yielding ``(name, item)``.
Items are ordered by a monotonic push stamp assigned when each
transformer pushes into its `StreamChannel`. This gives strict
arrival ordering across projections, unlike round-robin.
Args:
*names: Projection keys to interleave. Must match keys in
``extensions``.
Yields:
``(name, item)`` tuples in arrival order across the named
projections.
Each named channel is locked for the duration of iteration and
released when the generator completes, is closed, or raises.
Channels cannot be subscribed concurrently use `.tee(n)` if
you need fan-out.
Raises:
KeyError: If a name doesn't match a registered projection.
Example:
```python
for name, item in run.interleave("messages", "values"):
if name == "messages":
print("msg:", item)
else:
print("val:", item)
```
"""
from langgraph.stream.stream_channel import StreamChannel
channels: dict[str, StreamChannel[Any]] = {}
try:
for name in names:
ch = self.extensions[name]
if not isinstance(ch, StreamChannel):
raise TypeError(
f"interleave() requires StreamChannel projections, "
f"got {type(ch).__name__} for {name!r}"
)
if ch._is_async is None:
raise TypeError(
f"StreamChannel {name!r} has not been bound yet. "
"Register the transformer with a StreamMux first."
)
if ch._is_async:
raise TypeError(
f"StreamChannel {name!r} is bound to async mode — "
"sync interleave() cannot consume async channels."
)
if ch._subscribed:
raise RuntimeError(
f"StreamChannel {name!r} already has a subscriber; "
"use .tee(n) for fan-out."
)
ch._subscribed = True
channels[name] = ch
done: set[str] = set()
while len(done) < len(channels):
best: tuple[int, str] | None = None
for name, ch in channels.items():
if name in done:
continue
if ch._closed and not ch._items:
if ch._error is not None:
raise ch._error
done.add(name)
continue
if ch._items:
stamp = ch._items[0][0]
if best is None or stamp < best[0]:
best = (stamp, name)
if best is not None:
_stamp, item = channels[best[1]]._items.popleft()
yield (best[1], item)
else:
pump = self._mux._pump_fn
if pump is None or not pump():
before = len(done)
for name, ch in channels.items():
if name not in done and not ch._items:
if ch._closed:
if ch._error is not None:
raise ch._error
done.add(name)
if len(done) == before:
break
finally:
for ch in channels.values():
ch._subscribed = False
@beta(message="The v3 streaming protocol on Pregel is experimental.")
class AsyncGraphRunStream:
"""Async run stream with caller-driven pumping.
Async iteration on any projection drives the graph forward there
is no background task. Concurrent consumers share a single-flight
pump via an `asyncio.Lock`, so each awaiting cursor contributes one
event per acquisition. Backpressure comes from the logs: when a
subscribed log's buffer reaches `maxlen`, `apush` awaits the
subscriber to drain, which holds back the pump and paces the graph.
Projections are single-consumer a second `aiter(run.values)`
raises. Use `projection.tee(n)` for fan-out.
Use as an async context manager to guarantee clean shutdown on
early exit:
```python
async with await handler.astream(input) as run:
async for msg in run.messages:
...
```
!!! warning
Awaited from `Pregel.astream_events(version="v3")`, which is
experimental and may change.
"""
def __init__(
self,
graph_aiter: AsyncIterator[Any] | None,
mux: StreamMux,
*,
wire_pump: bool = True,
) -> None:
"""Initialize the async run stream.
Args:
graph_aiter: Async iterator over the graph's stream, or
`None` for nested run streams whose pump is driven by
an outer run (e.g. `AsyncSubgraphRunStream`).
mux: The StreamMux owning projections and the main log.
wire_pump: When True (default), bind `_apump_next` as the
mux's async pump callable. Subclasses that inherit a
parent pump via `StreamMux._make_child` should pass
False to preserve the parent binding.
"""
self._graph_aiter = graph_aiter
self._mux = mux
self.extensions: Mapping[str, Any] = MappingProxyType(mux.extensions)
self._exhausted = False
self._latest: dict[str, Any] | None = None
self._interrupted = False
self._interrupts: list[Any] = []
self._scope_list: list[str] = list(mux.scope)
self._pump_cond = asyncio.Condition()
self._pumping = False
for key in mux.native_keys:
setattr(self, key, mux.extensions[key])
if wire_pump:
self._wire_arequest_more(mux)
def _observe_event(self, event: ProtocolEvent) -> None:
"""Track values-event state for output/interrupted/interrupts."""
if event["method"] != "values":
return
params = event["params"]
if params["namespace"] != self._scope_list:
return
self._latest = params["data"]
interrupts = params.get("interrupts", ())
if interrupts:
self._interrupted = True
self._interrupts.extend(interrupts)
def _wire_arequest_more(self, mux: StreamMux) -> None:
"""Wire the async pull callback through the mux.
Mirrors `_wire_request_more`: routing through
`mux.bind_apump` lets child mini-muxes inherit the pump
callable so cursors on subgraph handles drive the root
pump.
"""
mux.bind_apump(self._apump_next)
async def _apump_next(self) -> bool:
"""Drive one pump step, or wait for the active pumper to drive one.
"Take-a-number" semantics: at most one task at a time calls
`graph_aiter.__anext__()` (asyncio iterators can't be advanced
concurrently). Other callers wait on a Condition that the
active pumper notifies after each step. This lets a "passive"
consumer one whose projection's buffer is being filled by the
active pumper's push — wake up as soon as its data lands,
instead of queueing on the pump and only observing its data one
graph event late.
`except Exception` is intentional `CancelledError` and other
`BaseException` subclasses propagate, matching asyncio's
cancellation contract.
Returns:
True if a pump step completed (by this task or another),
False if the graph is exhausted.
"""
async with self._pump_cond:
if self._exhausted or self._graph_aiter is None:
return False
if self._pumping:
# Another task is pumping; wait for its progress signal.
await self._pump_cond.wait()
return not self._exhausted
self._pumping = True
try:
try:
part = await self._graph_aiter.__anext__()
event = convert_to_protocol_event(part)
self._observe_event(event)
await self._mux.apush(event)
return True
except StopAsyncIteration:
self._exhausted = True
await self._mux.aclose()
return False
except Exception as e:
self._exhausted = True
await self._mux.afail(e)
return False
finally:
async with self._pump_cond:
self._pumping = False
self._pump_cond.notify_all()
async def abort(self) -> None:
"""Stop the run early.
Marks the stream exhausted, wakes any pump-waiters, and closes
the mux. Any `apush` blocked on backpressure wakes and returns
without appending. Idempotent.
"""
async with self._pump_cond:
if self._exhausted:
return
self._exhausted = True
self._pump_cond.notify_all()
try:
await self._mux.aclose()
except Exception:
pass
async def __aenter__(self) -> AsyncGraphRunStream:
return self
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
await self.abort()
async def output(self) -> dict[str, Any] | None:
"""Drive the run to completion and return the final state.
Methods (not properties) on the async lane so `run.output`
without `await` raises at type-check time instead of silently
yielding a coroutine object.
Example:
```python
output = await run.output()
```
Raises:
BaseException: If the run ended with an error.
"""
await _adrive_until_done(self._apump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._latest
async def interrupted(self) -> bool:
"""Drive the run to completion and return whether it was
interrupted.
Raises:
BaseException: If the run ended with an error.
"""
await _adrive_until_done(self._apump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._interrupted
async def interrupts(self) -> list[Any]:
"""Drive the run to completion and return interrupt payloads.
Raises:
BaseException: If the run ended with an error.
"""
await _adrive_until_done(self._apump_next)
if (err := self._mux._events._error) is not None:
raise err
return self._interrupts
def __aiter__(self) -> AsyncIterator[ProtocolEvent]:
"""Subscribe to the main event log and iterate protocol events."""
return self._mux._events.__aiter__()
class _SubgraphRunStreamMixin:
"""Subgraph metadata + parent-pump delegation shared by both lanes.
Inherits from `GraphRunStream` (or `AsyncGraphRunStream`) with
`graph_iter=None` + `wire_pump=False` the mini-mux is driven
by the parent's pump (inherited via `StreamMux._make_child`), and
the handle never pulls upstream itself. Pump-driving methods
delegate to the parent pump so `handle.output` and friends drive
the root run.
Subclasses set the parent pump function captured at construction
(`_parent_pump_fn` / `_parent_apump_fn`) and override
`_pump_next` / `_apump_next` to delegate to it.
Status is updated in place by `SubgraphTransformer`. Iterate
`run.subgraphs` to receive handles as subgraphs spawn, then
drill into projections inside the loop body **before** the next
pump cycle same lazy-subscribe constraint as root projections.
"""
path: tuple[str, ...]
graph_name: str | None
trigger_call_id: str | None
status: SubgraphStatus
error: str | None
_seen_terminal: bool
class SubgraphRunStream(GraphRunStream, _SubgraphRunStreamMixin):
"""Sync handle for a discovered subgraph (extends `GraphRunStream`)."""
def __init__(
self,
mux: StreamMux,
*,
path: tuple[str, ...],
graph_name: str | None = None,
trigger_call_id: str | None = None,
) -> None:
# Capture the parent-inherited pump before super().__init__
# touches anything; we delegate to it from `_pump_next`.
self._parent_pump_fn: Callable[[], bool] | None = mux._pump_fn
super().__init__(
graph_iter=None,
mux=mux,
wire_pump=False,
)
self.path = path
self.graph_name = graph_name
self.trigger_call_id = trigger_call_id
self.status = "started"
self.error = None
self._seen_terminal = False
def _pump_next(self) -> bool:
"""Delegate to the parent's pump.
Cursors on this handle's projections call here when their
buffers empty. Driving the parent fans events into our
mini-mux, transparently advancing the whole run.
"""
if (
self._exhausted
or self._seen_terminal
or self._mux._events._closed
or self._parent_pump_fn is None
):
return False
return self._parent_pump_fn()
class AsyncSubgraphRunStream(AsyncGraphRunStream, _SubgraphRunStreamMixin):
"""Async handle for a discovered subgraph (extends `AsyncGraphRunStream`)."""
def __init__(
self,
mux: StreamMux,
*,
path: tuple[str, ...],
graph_name: str | None = None,
trigger_call_id: str | None = None,
) -> None:
self._parent_apump_fn: Callable[[], Awaitable[bool]] | None = mux._apump_fn
super().__init__(
graph_aiter=None,
mux=mux,
wire_pump=False,
)
self.path = path
self.graph_name = graph_name
self.trigger_call_id = trigger_call_id
self.status = "started"
self.error = None
self._seen_terminal = False
async def _apump_next(self) -> bool:
"""Delegate to the parent's async pump."""
if (
self._exhausted
or self._seen_terminal
or self._mux._events._closed
or self._parent_apump_fn is None
):
return False
return await self._parent_apump_fn()
@@ -0,0 +1,341 @@
from __future__ import annotations
import asyncio
from collections import deque
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
from typing import TYPE_CHECKING, Generic, TypeVar
if TYPE_CHECKING:
from langgraph.stream._mux import StreamMux
T = TypeVar("T")
class StreamChannel(Generic[T]):
"""Single-consumer drainable queue for streaming events, with optional
protocol auto-forwarding.
When constructed with a `name`, the StreamMux auto-wires every
`push()` to also inject a `ProtocolEvent` into the main event stream
using the channel's name as the method. When constructed without a
name, the channel is local-only items are only visible to
in-process consumers that iterate the channel directly.
Items are popped off the front as the consumer advances there is
no retention beyond what's currently queued. A channel accepts
exactly one subscriber; a second `__iter__` / `__aiter__` call
raises. Use `tee(n)` / `atee(n)` for fan-out.
Starts unbound neither `__iter__` nor `__aiter__` is available
until the StreamMux calls `_bind(is_async)`. After binding, only
the matching iteration protocol works; the other raises `TypeError`.
Pump wiring (set by the run stream, not by `_bind`):
- `_request_more`: sync pump callable, returns True if a new
event was produced.
- `_arequest_more`: async pump coroutine factory, same contract.
Memory is bounded by caller pace: both sync and async use caller-
driven pumps, so each cursor advance produces at most one event.
Lazy-subscribe: `push` appends to the local buffer only when a
subscriber has registered. Auto-forward via `_wire_fn` always fires
regardless of subscription state.
Lifecycle (`close` / `fail`) is managed by the mux transformers
don't need to close their channels manually.
"""
def __init__(self, name: str | None = None, *, maxlen: int | None = None) -> None:
"""Initialize the channel.
Args:
name: Optional protocol channel name. When set, the
StreamMux wires every `push()` to also inject a
`ProtocolEvent` into the main event stream. Surfaced
on the wire as `custom:<name>` for user-defined
transformers, or as `<name>` for channels owned by a
native transformer (`_native = True`). When `None`,
the channel is local-only.
maxlen: Accepted for forward compatibility; currently
unused. The caller-driven pump bounds memory naturally
for single-consumer use.
Raises:
ValueError: If `maxlen` is not a positive integer or `None`.
"""
if maxlen is not None and maxlen <= 0:
raise ValueError("StreamChannel maxlen must be a positive int or None")
self.name = name
self._items: deque[tuple[int, T]] = deque()
self._maxlen: int | None = maxlen
self._closed = False
self._error: BaseException | None = None
self._is_async: bool | None = None
self._subscribed = False
self._request_more: Callable[[], bool] | None = None
self._arequest_more: Callable[[], Awaitable[bool]] | None = None
self._wire_fn: Callable[[T], None] | None = None
self._mux: StreamMux | None = None
# ------------------------------------------------------------------
# Binding
# ------------------------------------------------------------------
def _bind_mux(self, mux: StreamMux) -> None:
self._mux = mux
def _bind(self, *, is_async: bool) -> None:
"""Bind this channel to sync or async mode.
Called by the StreamMux after transformer registration. Must be
called exactly once before any iteration.
Args:
is_async: True to enable async iteration, False for sync.
Raises:
RuntimeError: If the channel has already been bound.
"""
if self._is_async is not None:
raise RuntimeError("StreamChannel is already bound")
self._is_async = is_async
# ------------------------------------------------------------------
# Mux wiring (not called by transformers directly)
# ------------------------------------------------------------------
def _wire(self, fn: Callable[[T], None]) -> None:
"""Install the auto-forward callback (called by StreamMux)."""
self._wire_fn = fn
# ------------------------------------------------------------------
# Producer API
# ------------------------------------------------------------------
def push(self, item: T) -> None:
"""Append an item. Auto-forwards if wired.
The local buffer append is a no-op when no subscriber is
registered, but auto-forwarding always fires so wired events
reach the main event log regardless of subscription state.
Items are stored as `(stamp, item)` tuples where stamp is a
monotonic counter from the owning mux. Stamps are stripped by
the default cursors; raw stamped tuples are visible on `_items`.
Raises:
RuntimeError: If the channel is closed (and subscribed).
"""
if self._subscribed:
if self._closed:
raise RuntimeError("Cannot push to a closed StreamChannel")
stamp = self._mux._next_push_seq() if self._mux is not None else 0
self._items.append((stamp, item))
if self._wire_fn is not None:
self._wire_fn(item)
def close(self) -> None:
"""Mark the channel as complete."""
self._closed = True
def fail(self, err: BaseException) -> None:
"""Mark the channel as errored.
Args:
err: The exception to surface to the subscriber.
"""
self._error = err
self._closed = True
# ------------------------------------------------------------------
# Sync iteration (caller-driven pump)
# ------------------------------------------------------------------
def __iter__(self) -> Iterator[T]:
"""Subscribe and return a sync cursor. Can be called only once.
Raises:
TypeError: If the channel is unbound or bound to async mode.
RuntimeError: If the channel already has a subscriber.
"""
if self._is_async is None:
raise TypeError(
"StreamChannel has not been bound yet. "
"Register the transformer with a StreamMux first."
)
if self._is_async:
raise TypeError(
"This StreamChannel is bound to async mode — use 'async for' instead."
)
if self._subscribed:
raise RuntimeError(
"StreamChannel already has a subscriber; use .tee(n) for fan-out."
)
self._subscribed = True
return self._sync_cursor()
def _sync_cursor(self) -> Iterator[T]:
while True:
if self._items:
_stamp, item = self._items.popleft()
yield item
elif self._closed:
if self._error is not None:
raise self._error
return
elif self._request_more is not None:
if not self._request_more():
if not self._items and not self._closed:
return
else:
return
# ------------------------------------------------------------------
# Async iteration (caller-driven pump)
# ------------------------------------------------------------------
def __aiter__(self) -> AsyncIterator[T]:
"""Subscribe and return an async cursor. Can be called only once.
Raises:
TypeError: If the channel is unbound or bound to sync mode.
RuntimeError: If the channel already has a subscriber.
"""
if self._is_async is None:
raise TypeError(
"StreamChannel has not been bound yet. "
"Register the transformer with a StreamMux first."
)
if not self._is_async:
raise TypeError(
"This StreamChannel is bound to sync mode — use 'for' instead."
)
if self._subscribed:
raise RuntimeError(
"StreamChannel already has a subscriber; use .atee(n) for fan-out."
)
self._subscribed = True
return self._async_cursor()
async def _async_cursor(self) -> AsyncIterator[T]:
while True:
if self._items:
_stamp, item = self._items.popleft()
yield item
elif self._closed:
if self._error is not None:
raise self._error
return
elif self._arequest_more is not None:
if not await self._arequest_more():
if not self._items and not self._closed:
return
else:
return
# ------------------------------------------------------------------
# Fan-out via tee
# ------------------------------------------------------------------
def tee(self, n: int = 2) -> tuple[Iterator[T], ...]:
"""Subscribe and return `n` independent sync iterators.
Each branch has its own buffer; items pulled from the
underlying cursor are copied into every branch. Branches are
naturally bounded by caller pace since the sync pump is
caller-driven.
Args:
n: Number of branches to create. Must be >= 1.
Returns:
A tuple of `n` iterators over the same underlying stream.
Raises:
TypeError: If the channel is unbound or bound to async mode.
RuntimeError: If the channel already has a subscriber.
ValueError: If `n` < 1.
"""
if n < 1:
raise ValueError("tee() requires n >= 1")
source = self.__iter__()
buffers: list[deque[T]] = [deque() for _ in range(n)]
exhausted = [False]
def branch(i: int) -> Iterator[T]:
buf = buffers[i]
while True:
if buf:
yield buf.popleft()
elif exhausted[0]:
return
else:
try:
item = next(source)
except StopIteration:
exhausted[0] = True
return
for b in buffers:
b.append(item)
return tuple(branch(i) for i in range(n))
def atee(self, n: int = 2) -> tuple[AsyncIterator[T], ...]:
"""Subscribe and return `n` independent async iterators.
Caller-driven fan-out: each branch's `__anext__` either pops
from its own buffer or, under a shared `asyncio.Lock`, pulls
one item from the underlying cursor and distributes it to
every branch's buffer.
Args:
n: Number of branches to create. Must be >= 1.
Returns:
A tuple of `n` async iterators over the same underlying
stream.
Raises:
TypeError: If the channel is unbound or bound to sync mode.
RuntimeError: If the channel already has a subscriber.
ValueError: If `n` < 1.
"""
if n < 1:
raise ValueError("atee() requires n >= 1")
source = self.__aiter__()
buffers: list[deque[T]] = [deque() for _ in range(n)]
exhausted = [False]
error: list[BaseException | None] = [None]
lock = asyncio.Lock()
async def branch(i: int) -> AsyncIterator[T]:
buf = buffers[i]
while True:
if buf:
yield buf.popleft()
continue
if exhausted[0]:
if error[0] is not None:
raise error[0]
return
async with lock:
if buf or exhausted[0]:
continue
try:
item = await source.__anext__()
except StopAsyncIteration:
exhausted[0] = True
continue
except Exception as e:
error[0] = e
exhausted[0] = True
continue
for b in buffers:
b.append(item)
return tuple(branch(i) for i in range(n))
@@ -0,0 +1,928 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Literal, cast
from langchain_core.language_models._compat_bridge import message_to_events
from langchain_core.language_models.chat_model_stream import (
AsyncChatModelStream,
ChatModelStream,
)
from langchain_core.messages import AIMessageChunk, BaseMessage
from langchain_protocol.protocol import MessagesData
from typing_extensions import NotRequired, TypedDict
from langgraph.errors import GraphDrained, GraphInterrupt
from langgraph.stream._types import ProtocolEvent, StreamTransformer
from langgraph.stream.run_stream import AsyncSubgraphRunStream, SubgraphRunStream
from langgraph.stream.stream_channel import StreamChannel
if TYPE_CHECKING:
from collections.abc import Awaitable, Callable
from langgraph.stream._mux import StreamMux
_logger = logging.getLogger(__name__)
class ValuesTransformer(StreamTransformer):
"""Capture values events as a drainable stream of state snapshots.
Provides the `run.values` projection. `run.output`,
`run.interrupted` and `run.interrupts` are tracked directly
by the run stream and do not depend on this transformer.
Native transformer projection keys are exposed as direct
attributes on the run stream (e.g. `run.values`).
Only values events at the run's own level are captured; snapshots
from deeper subgraphs are left in the main event log but excluded
from the projection. "Own level" is defined by `scope`, which
`stream_events(version="v3")` / `astream_events(version="v3")` populate from the caller's
checkpoint namespace so that a nested `stream_events(version="v3")` call still
sees its own root snapshots.
"""
_native = True
required_stream_modes = ("values",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[dict[str, Any]] = StreamChannel()
self._latest: dict[str, Any] | None = None
self._interrupted = False
self._interrupts: list[Any] = []
# Cached as a list once for cheap equality with the protocol
# event's `namespace` field, which is `list[str]`.
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"values": self._log}
@property
def error(self) -> BaseException | None:
"""The error that ended the run, or `None` if it succeeded.
Set by the mux when it auto-fails the projection log.
"""
return self._log._error
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "values":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._latest = params["data"]
interrupts = params.get("interrupts", ())
if interrupts:
self._interrupted = True
self._interrupts.extend(interrupts)
self._log.push(params["data"])
return True
class CustomTransformer(StreamTransformer):
"""Capture custom events as a drainable stream of arbitrary payloads.
Nodes emit custom data via `get_stream_writer()`. This transformer
surfaces those events on `run.custom` as a `StreamChannel[Any]`,
preserving payloads in arrival order.
Only events at the run's own scope are captured; custom data from
deeper subgraphs is available on the respective subgraph handle's
`.custom` projection.
Native transformer `run.custom` is a direct attribute.
"""
_native = True
required_stream_modes = ("custom",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[Any] = StreamChannel()
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"custom": self._log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "custom":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._log.push(params["data"])
return True
class UpdatesTransformer(StreamTransformer):
"""Capture updates events as a drainable stream of node outputs.
Surfaces `stream_mode="updates"` data on `run.updates` as a
`StreamChannel[dict[str, Any]]`. Each item is a dict mapping a node
(or task) name to the update it returned after a step.
Only events at the run's own scope are captured; updates from deeper
subgraphs are available on the respective subgraph handle's
`.updates` projection.
Native transformer `run.updates` is a direct attribute.
"""
_native = True
required_stream_modes = ("updates",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[dict[str, Any]] = StreamChannel()
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"updates": self._log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "updates":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._log.push(params["data"])
return True
class MessagesTransformer(StreamTransformer):
"""Capture messages events as ChatModelStream objects.
The messages projection yields one `ChatModelStream` (or
`AsyncChatModelStream`) per LLM call. Consumers iterate
`run.messages` to get stream handles, then use each handle's typed
projections (`.text`, `.reasoning`, `.tool_calls`, `.usage`,
`.output`) for per-message content.
Two input shapes are handled (via `params["data"] = (payload,
metadata)` from `StreamMessagesHandler`):
1. Protocol event (dict with `"event"` key) emitted by
`stream_events(version="v3")` / `astream_events(version="v3")` via the `on_stream_event`
callback. Routed to an existing `ChatModelStream` by
`metadata["run_id"]`. A `message-start` event creates a new
stream; `message-finish` closes it.
2. Whole `AIMessage` emitted from `on_chain_end` when a node
returns a finalized message. Replayed as a synthetic protocol
event lifecycle via `message_to_events`, then the
already-complete stream is pushed to the log.
V1 `AIMessageChunk` tuples (from `on_llm_new_token`) are not
streamed into this projection: chat models that want to populate
`run.messages` with content-block streaming must use
`stream_events(version="v3")` / `astream_events(version="v3")`. Models called via the legacy
`stream()` method still surface their final `AIMessage` via
`on_chain_end` when a node returns it as state.
Only events at the run's own level are projected; tokens from
deeper subgraphs are left in the main event log but excluded from
`.messages`. "Own level" is defined by `scope`, which
`stream_events(version="v3")` / `astream_events(version="v3")` populate from the caller's checkpoint
namespace so that a `stream_events(version="v3")` call inside a node still sees its
own root chat model streams on `.messages`. Consumers that need
subgraph tokens should iterate the raw event stream or register a
custom transformer.
Native transformer the `messages` projection is exposed as a
direct attribute on the run stream.
"""
_native = True
required_stream_modes = ("messages",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[ChatModelStream] = StreamChannel()
# Correlate protocol events back to a ChatModelStream by run_id
# (attached to the event's metadata by StreamMessagesHandler).
self._by_run: dict[str, ChatModelStream] = {}
self._pump_fn: Callable[[], bool] | None = None
self._apump_fn: Callable[[], Awaitable[bool]] | None = None
# Cached as a list once for cheap equality with the protocol
# event's `namespace` field, which is `list[str]`.
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"messages": self._log}
def _bind_pump(self, fn: Callable[[], bool]) -> None:
"""Wire the sync pull callback. Called by GraphRunStream._wire_request_more."""
self._pump_fn = fn
def _bind_apump(self, fn: Callable[[], Awaitable[bool]]) -> None:
"""Wire the async pull callback.
Called by `AsyncGraphRunStream._wire_arequest_more` so each
`AsyncChatModelStream` this transformer creates can drive the
shared graph pump from its projection cursors.
"""
self._apump_fn = fn
def _make_stream(
self,
*,
namespace: list[str],
node: str | None,
message_id: str | None,
) -> ChatModelStream:
"""Create a ChatModelStream (sync) or AsyncChatModelStream (async).
Wires whichever pump is bound. Prefers the async pump so nested
iteration under `AsyncGraphRunStream` drives the graph forward
without a background task. The unwired fallback (no pump bound)
is used by unit tests that dispatch events manually.
"""
if self._apump_fn is not None:
astream = AsyncChatModelStream(
namespace=namespace,
node=node,
message_id=message_id,
)
astream.set_arequest_more(self._apump_fn)
return astream
if self._pump_fn is not None:
stream: ChatModelStream = ChatModelStream(
namespace=namespace,
node=node,
message_id=message_id,
)
stream.set_request_more(self._pump_fn)
return stream
return AsyncChatModelStream(
namespace=namespace,
node=node,
message_id=message_id,
)
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "messages":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
payload, metadata = params["data"]
node: str | None = metadata.get("langgraph_node")
run_id = str(metadata.get("run_id", "")) if metadata else ""
if isinstance(payload, dict) and "event" in payload:
self._route_protocol_event(
cast("MessagesData", payload), run_id=run_id, node=node
)
elif isinstance(payload, BaseMessage) and not isinstance(
payload, AIMessageChunk
):
self._route_whole_message(payload, node=node)
# Legacy AIMessageChunk tuples (from on_llm_new_token) are ignored;
# v1 streaming callers must switch to stream_events(version="v3") to populate this
# projection.
return True
def _route_protocol_event(
self,
event: MessagesData,
*,
run_id: str,
node: str | None,
) -> None:
event_type = event.get("event")
if event_type == "message-start":
message_id = event.get("message_id")
stream = self._make_stream(
namespace=[],
node=node,
message_id=str(message_id) if message_id is not None else None,
)
self._by_run[run_id] = stream
self._log.push(stream)
stream.dispatch(event)
elif run_id in self._by_run:
stream = self._by_run[run_id]
stream.dispatch(event)
if event_type == "message-finish":
del self._by_run[run_id]
def _route_whole_message(self, message: BaseMessage, *, node: str | None) -> None:
stream = self._make_stream(namespace=[], node=node, message_id=message.id)
for evt in message_to_events(message, message_id=message.id):
stream.dispatch(evt)
self._log.push(stream)
def finalize(self) -> None:
"""Clear any routing state — streams close themselves via `message-finish`."""
self._by_run.clear()
def fail(self, err: BaseException) -> None:
"""Propagate run error to any streams still open when the graph fails."""
for stream in list(self._by_run.values()):
stream.fail(err)
self._by_run.clear()
SubgraphStatus = Literal["started", "completed", "failed", "interrupted", "drained"]
def _parse_ns_segment(segment: str) -> tuple[str, str | None]:
"""Split a namespace segment into `(graph_name, trigger_call_id)`.
Segments are formatted `node_name:task_id` by `prepare_next_tasks`.
Returns `(segment, None)` if no `:` is present.
"""
name, sep, task_id = segment.partition(":")
return name, task_id if sep else None
class LifecyclePayload(TypedDict, total=False):
"""Payload of a lifecycle event surfaced on the `lifecycle` channel.
Auto-forwarded as `lifecycle` protocol events (no `custom:` prefix
because `LifecycleTransformer` is a native transformer) so remote
SDK clients receive the same data in-process consumers see via
`run.lifecycle`.
"""
event: SubgraphStatus
namespace: list[str]
graph_name: NotRequired[str]
trigger_call_id: NotRequired[str]
error: NotRequired[str]
class _TasksLifecycleBase(StreamTransformer):
"""Shared bookkeeping for `tasks`-event-driven lifecycle inference.
Both `LifecycleTransformer` (wire-serializable channel) and
`SubgraphTransformer` (in-process navigation handles) discover
subgraphs by watching the same `tasks` stream `started` on the
first event at a tracked namespace, terminal status when the
parent's `TaskResultPayload` arrives. Centralizing the dispatch
+ open-set bookkeeping here keeps the inference rules from
drifting between the two surfaces.
Subclasses provide three template-method hooks:
- `_should_track(ns)` scope filter (e.g. multi-depth vs
direct-children-only).
- `_on_started(ns, graph_name, trigger_call_id)` first sighting
action (push payload / build handle / etc.). Called once per
discovered namespace.
- `_on_terminal(ns, status, error)` terminal action (push
terminal payload / mark handle status). Called once per
tracked namespace at result time, or via `finalize` / `fail`
sweeps if no parent result arrived.
Tasks events are suppressed from the main event log (`process`
returns False) they're folded into whichever projection the
subclass populates; consumers iterating the raw protocol stream
see the higher-level view.
"""
required_stream_modes = ("tasks",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._seen: set[tuple[str, ...]] = set()
# Maps tracked namespace -> task_id of the parent task whose
# `TaskResultPayload` will close it.
self._open: dict[tuple[str, ...], str] = {}
# --- Template-method hooks (subclass overrides) ---
def _should_track(self, ns: tuple[str, ...]) -> bool:
"""Scope filter — return True iff `ns` is in this transformer's region."""
raise NotImplementedError
def _on_started(
self,
ns: tuple[str, ...],
graph_name: str | None,
trigger_call_id: str | None,
) -> None:
"""Fired once per discovered namespace (first observed task event)."""
raise NotImplementedError
def _on_terminal(
self,
ns: tuple[str, ...],
status: SubgraphStatus,
error: str | None,
) -> None:
"""Fired once per tracked namespace when its parent's result arrives,
or via finalize/fail safety-net sweeps.
"""
raise NotImplementedError
# --- Dispatch + bookkeeping (shared) ---
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "tasks":
return True
ns = tuple(event["params"]["namespace"])
data = event["params"]["data"]
if "result" in data:
self._handle_task_result(ns, data)
else:
self._handle_task_start(ns)
# Tasks events are folded into the synthesized projections;
# suppress from the main event log so iterators don't double-see
# the same information in two shapes.
return False
def _handle_task_start(self, ns: tuple[str, ...]) -> None:
if not self._should_track(ns) or ns in self._seen:
return
self._seen.add(ns)
graph_name, trigger_call_id = _parse_ns_segment(ns[-1])
self._on_started(ns, graph_name or None, trigger_call_id)
if trigger_call_id is not None:
self._open[ns] = trigger_call_id
def _pop_terminal_transitions(
self, ns: tuple[str, ...], data: dict[str, Any]
) -> list[tuple[tuple[str, ...], SubgraphStatus, str | None]]:
"""Return and remove tracked children closed by this task result."""
result_id = data.get("id")
if not result_id:
return []
transitions: list[tuple[tuple[str, ...], SubgraphStatus, str | None]] = []
for child_ns, parent_task_id in list(self._open.items()):
if child_ns[:-1] != ns or parent_task_id != result_id:
continue
status, error = _terminal_from_result(data)
transitions.append((child_ns, status, error))
del self._open[child_ns]
return transitions
def _handle_task_result(self, ns: tuple[str, ...], data: dict[str, Any]) -> None:
for child_ns, status, error in self._pop_terminal_transitions(ns, data):
self._on_terminal(child_ns, status, error)
def finalize(self) -> None:
"""Emit `completed` for any tracked namespace still open at run end."""
for ns in list(self._open):
self._on_terminal(ns, "completed", None)
self._open.clear()
def fail(self, err: BaseException) -> None:
"""Emit terminal status for any tracked namespace still open."""
status, error_str = _status_from_exception(err)
for ns in list(self._open):
self._on_terminal(ns, status, error_str)
self._open.clear()
def _status_from_exception(err: BaseException) -> tuple[SubgraphStatus, str | None]:
"""Map a run exception to a subgraph terminal status and error string."""
if isinstance(err, GraphDrained):
return "drained", None
if isinstance(err, GraphInterrupt):
return "interrupted", None
return "failed", str(err)
def _terminal_from_result(
payload: dict[str, Any],
) -> tuple[SubgraphStatus, str | None]:
"""Map a `TaskResultPayload` to a `(status, error)` pair.
Order matters: a result with both `error` and `interrupts` prefers
the interrupt classification, since `GraphInterrupt` manifests as
a populated `interrupts` list, not as `error`.
"""
if payload.get("interrupts"):
return "interrupted", None
error = payload.get("error")
if error:
return "failed", str(error)
return "completed", None
class LifecycleTransformer(_TasksLifecycleBase):
"""Surface subgraph lifecycle as `lifecycle` protocol events.
Pushes `LifecyclePayload` to a `StreamChannel` named `lifecycle`.
The channel is auto-forwarded by the mux so payloads land in the
main event log under `method = "lifecycle"` (native transformer
no `custom:` prefix) visible to remote SDK clients over the
wire and to in-process consumers via `run.lifecycle`.
Tracks subgraphs at every depth strictly below the transformer's
scope, so a graph subgraph subgraph chain produces lifecycle
events for both nested levels in a flat stream.
Native transformer projection key `lifecycle` is exposed as
`run.lifecycle`.
"""
_native = True
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._channel: StreamChannel[LifecyclePayload] = StreamChannel("lifecycle")
def init(self) -> dict[str, Any]:
return {"lifecycle": self._channel}
def _should_track(self, ns: tuple[str, ...]) -> bool:
depth = len(self.scope)
return len(ns) > depth and ns[:depth] == self.scope
def _on_started(
self,
ns: tuple[str, ...],
graph_name: str | None,
trigger_call_id: str | None,
) -> None:
if trigger_call_id is None:
# Without a task id we can't correlate a parent-result
# event back to this namespace — skip the started payload
# and rely on finalize/fail to close.
return
payload: LifecyclePayload = {"event": "started", "namespace": list(ns)}
if graph_name:
payload["graph_name"] = graph_name
payload["trigger_call_id"] = trigger_call_id
self._channel.push(payload)
def _on_terminal(
self,
ns: tuple[str, ...],
status: SubgraphStatus,
error: str | None,
) -> None:
payload: LifecyclePayload = {"event": status, "namespace": list(ns)}
if error is not None:
payload["error"] = error
self._channel.push(payload)
class SubgraphTransformer(_TasksLifecycleBase):
"""Discover subgraph invocations as in-process navigation handles.
Per discovered direct-child subgraph, builds a `SubgraphRunStream`
(or `AsyncSubgraphRunStream`) wrapping a child mini-mux scoped to
the subgraph's namespace. Consumers iterate `run.subgraphs` to
receive handles, then drill into `handle.values` / `handle.messages`
/ `handle.subgraphs` (recursive grandchildren) / `handle.lifecycle`.
Each mini-mux owns its own scope and uses its own
`SubgraphTransformer` to discover its direct children, so
grandchildren live on the child handle never on the root's
`subgraphs` log. Forwarding events into the matching child mini-mux
is what keeps the child's projections populated.
Native transformer `subgraphs` is exposed as `run.subgraphs`.
"""
_native = True
supports_sync = True
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[SubgraphRunStream | AsyncSubgraphRunStream] = (
StreamChannel()
)
self._handles: dict[
tuple[str, ...], SubgraphRunStream | AsyncSubgraphRunStream
] = {}
self._mux: StreamMux | None = None
def init(self) -> dict[str, Any]:
return {"subgraphs": self._log}
def _on_register(self, mux: Any) -> None:
self._mux = mux
def _should_track(self, ns: tuple[str, ...]) -> bool:
# Direct children only — grandchildren are picked up by the
# child mini-mux's own SubgraphTransformer.
depth = len(self.scope)
return len(ns) == depth + 1 and ns[:depth] == self.scope
def _on_started(
self,
ns: tuple[str, ...],
graph_name: str | None,
trigger_call_id: str | None,
) -> None:
if self._mux is None:
return
try:
child_mux = self._mux._make_child(ns)
except RuntimeError:
return
handle_cls = AsyncSubgraphRunStream if child_mux.is_async else SubgraphRunStream
handle = handle_cls(
mux=child_mux,
path=ns,
graph_name=graph_name,
trigger_call_id=trigger_call_id,
)
self._handles[ns] = handle
self._log.push(handle)
def _on_terminal(
self,
ns: tuple[str, ...],
status: SubgraphStatus,
error: str | None,
) -> None:
handle = self._handles.get(ns)
if handle is None or not self._mark_terminal(handle, status, error):
return
self._close_or_fail_handle(handle, status, error)
async def _aon_terminal(
self,
ns: tuple[str, ...],
status: SubgraphStatus,
error: str | None,
) -> None:
handle = self._handles.get(ns)
if handle is None or not self._mark_terminal(handle, status, error):
return
await self._aclose_or_fail_handle(handle, status, error)
def _mark_terminal(
self,
handle: SubgraphRunStream | AsyncSubgraphRunStream,
status: SubgraphStatus,
error: str | None,
) -> bool:
"""Mark a handle terminal once. Returns True on first transition."""
if handle._seen_terminal:
return False
handle.status = status
if error is not None and handle.error is None:
handle.error = error
handle._seen_terminal = True
return True
def _close_or_fail_handle(
self,
handle: SubgraphRunStream | AsyncSubgraphRunStream,
status: SubgraphStatus,
error: str | None,
) -> None:
if handle._mux is None or handle._mux._events._closed:
return
if status == "failed":
handle._mux.fail(RuntimeError(error or "Subgraph failed"))
else:
handle._mux.close()
async def _aclose_or_fail_handle(
self,
handle: SubgraphRunStream | AsyncSubgraphRunStream,
status: SubgraphStatus,
error: str | None,
) -> None:
if handle._mux is None or handle._mux._events._closed:
return
if status == "failed":
await handle._mux.afail(RuntimeError(error or "Subgraph failed"))
else:
await handle._mux.aclose()
def _handle_for_event(
self, event: ProtocolEvent
) -> SubgraphRunStream | AsyncSubgraphRunStream | None:
ns = tuple(event["params"]["namespace"])
depth = len(self.scope)
if len(ns) < depth + 1:
return None
handle = self._handles.get(ns[: depth + 1])
if handle is None or handle._mux is None or handle._mux._events._closed:
return None
return handle
def process(self, event: ProtocolEvent) -> bool:
# Run tasks bookkeeping first so a `started` handle exists
# by the time we forward the event to the child mini-mux.
keep = super().process(event)
handle = self._handle_for_event(event)
if handle is not None:
handle._observe_event(event)
handle._mux.push(event)
return keep
async def aprocess(self, event: ProtocolEvent) -> bool:
# Async counterpart: repeats the tasks bookkeeping here so
# child mini-muxes receive events through their async lane.
if event["method"] == "tasks":
ns = tuple(event["params"]["namespace"])
data = event["params"]["data"]
if "result" in data:
for child_ns, status, error in self._pop_terminal_transitions(ns, data):
await self._aon_terminal(child_ns, status, error)
else:
self._handle_task_start(ns)
keep = False
else:
keep = True
handle = self._handle_for_event(event)
if handle is not None:
handle._observe_event(event)
await handle._mux.apush(event)
return keep
def _complete_open_handles(self) -> BaseException | None:
first_error: BaseException | None = None
for ns in list(self._open):
try:
self._on_terminal(ns, "completed", None)
except BaseException as e:
if first_error is None:
first_error = e
self._open.clear()
for handle in self._handles.values():
if self._mark_terminal(handle, "completed", None):
try:
self._close_or_fail_handle(handle, "completed", None)
except BaseException as e:
if first_error is None:
first_error = e
return first_error
async def _acomplete_open_handles(self) -> BaseException | None:
first_error: BaseException | None = None
for ns in list(self._open):
try:
await self._aon_terminal(ns, "completed", None)
except BaseException as e:
if first_error is None:
first_error = e
self._open.clear()
for handle in self._handles.values():
if self._mark_terminal(handle, "completed", None):
try:
await self._aclose_or_fail_handle(handle, "completed", None)
except BaseException as e:
if first_error is None:
first_error = e
return first_error
def finalize(self) -> None:
first_error = self._complete_open_handles()
if first_error is not None:
raise first_error
async def afinalize(self) -> None:
first_error = await self._acomplete_open_handles()
if first_error is not None:
raise first_error
def fail(self, err: BaseException) -> None:
status, error_str = _status_from_exception(err)
self._open.clear()
for handle in self._handles.values():
self._mark_terminal(handle, status, error_str)
if handle._mux is not None and not handle._mux._events._closed:
try:
handle._mux.fail(err)
except Exception:
_logger.warning(
"Error failing subgraph mini-mux at %s; "
"subscribers may not see the terminal error.",
handle.path,
exc_info=True,
)
async def afail(self, err: BaseException) -> None:
status, error_str = _status_from_exception(err)
self._open.clear()
for handle in self._handles.values():
self._mark_terminal(handle, status, error_str)
if handle._mux is not None and not handle._mux._events._closed:
try:
await handle._mux.afail(err)
except Exception:
_logger.warning(
"Error failing subgraph mini-mux at %s; "
"subscribers may not see the terminal error.",
handle.path,
exc_info=True,
)
class CheckpointsTransformer(StreamTransformer):
"""Capture checkpoint events as a drainable stream.
Surfaces `stream_mode="checkpoints"` data on `run.checkpoints` as
a `StreamChannel[dict[str, Any]]`. Each item is in the same format
as returned by `get_state()`.
Checkpoint events are only emitted when a checkpointer is configured
on the graph. When no checkpointer is present, the projection exists
but receives no events.
Only events at the run's own scope are captured; checkpoint data from
deeper subgraphs is available on the respective subgraph handle's
`.checkpoints` projection.
Native transformer `run.checkpoints` is a direct attribute.
"""
_native = True
required_stream_modes = ("checkpoints",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[dict[str, Any]] = StreamChannel()
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"checkpoints": self._log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "checkpoints":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._log.push(params["data"])
return True
class DebugTransformer(StreamTransformer):
"""Capture debug events as a drainable stream.
Surfaces `stream_mode="debug"` data on `run.debug` as a
`StreamChannel[dict[str, Any]]`. Each item is a debug event with
step-level detail (checkpoint snapshots, task payloads, and
task results wrapped with step number and timestamp).
Only events at the run's own scope are captured; debug data from
deeper subgraphs is available on the respective subgraph handle's
`.debug` projection.
Native transformer `run.debug` is a direct attribute.
"""
_native = True
required_stream_modes = ("debug",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[dict[str, Any]] = StreamChannel()
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"debug": self._log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "debug":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._log.push(params["data"])
return True
class TasksTransformer(StreamTransformer):
"""Capture raw task events as a drainable stream.
Surfaces `stream_mode="tasks"` data on `run.tasks` as a
`StreamChannel[dict[str, Any]]`. Each item is a task payload
(start or result).
`LifecycleTransformer` and `SubgraphTransformer` also consume
`tasks` events for subgraph discovery and lifecycle tracking.
This transformer captures the raw payloads independently for
consumers who need task-level detail.
Only events at the run's own scope are captured; task data from
deeper subgraphs is available on the respective subgraph handle's
`.tasks` projection.
Native transformer `run.tasks` is a direct attribute.
"""
_native = True
required_stream_modes = ("tasks",)
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._log: StreamChannel[dict[str, Any]] = StreamChannel()
self._scope_list: list[str] = list(scope)
def init(self) -> dict[str, Any]:
return {"tasks": self._log}
def process(self, event: ProtocolEvent) -> bool:
if event["method"] != "tasks":
return True
params = event["params"]
if params["namespace"] != self._scope_list:
return True
self._log.push(params["data"])
return True
+100 -4
View File
@@ -4,6 +4,7 @@ import sys
from collections import deque
from collections.abc import Callable, Hashable, Sequence
from dataclasses import asdict, dataclass
from datetime import timedelta
from typing import (
TYPE_CHECKING,
Any,
@@ -67,6 +68,7 @@ __all__ = (
"CheckpointPayload",
"DebugPayload",
"RetryPolicy",
"TimeoutPolicy",
"CachePolicy",
"Interrupt",
"StateUpdate",
@@ -423,6 +425,83 @@ class RetryPolicy(NamedTuple):
"""List of exception classes that should trigger a retry, or a callable that returns `True` for exceptions that should trigger a retry."""
def _coerce_timeout_seconds(
value: float | timedelta | None, *, field: str
) -> float | None:
if value is None:
return None
seconds = value.total_seconds() if isinstance(value, timedelta) else float(value)
if seconds <= 0:
raise ValueError(f"{field} must be greater than 0")
return seconds
@dataclass(**_DC_KWARGS)
class TimeoutPolicy:
"""Configuration for timing out node attempts.
!!! note "Cooperative cancellation"
Timeouts rely on asyncio cancellation. If your node uses synchronous
time.sleep() or other CPU-bound work that blocks the GIL, the timeout will not
be fired until after the event loop has been released.
!!! note "Inline callback dispatch"
Under `refresh_on="auto"`, an internal handler refreshes the timeout on any
callback event that occurs in the execution of the node or its nested descendants.
"""
run_timeout: float | timedelta | None = None
"""Hard wall-clock cap (in seconds) for a single node attempt.
This timeout is never refreshed by progress signals or `runtime.heartbeat()`.
"""
idle_timeout: float | timedelta | None = None
"""Maximum time (in seconds) a single node attempt may go without observable progress."""
refresh_on: Literal["auto", "heartbeat"] = "auto"
"""Which signals refresh `idle_timeout`.
`"auto"` refreshes on standard graph progress signals and explicit heartbeats.
`"heartbeat"` refreshes only on explicit `runtime.heartbeat()` calls.
"""
@classmethod
def coerce(
cls, value: float | timedelta | TimeoutPolicy | None
) -> TimeoutPolicy | None:
"""Normalize a timeout value to positive-second policy fields."""
if value is None:
return None
if isinstance(value, TimeoutPolicy):
# Fast path: a policy already produced by coerce() has float
# timeouts and a validated refresh_on, so we can return it as-is.
# `frozen=True` makes this safe to share.
rt, it = value.run_timeout, value.idle_timeout
if (
value.refresh_on in ("auto", "heartbeat")
and (rt is None or (type(rt) is float and rt > 0))
and (it is None or (type(it) is float and it > 0))
and (rt is not None or it is not None)
):
return value
else:
value = cls(run_timeout=value)
if value.refresh_on not in ("auto", "heartbeat"):
raise ValueError("refresh_on must be 'auto' or 'heartbeat'")
run_timeout = _coerce_timeout_seconds(value.run_timeout, field="run_timeout")
idle_timeout = _coerce_timeout_seconds(value.idle_timeout, field="idle_timeout")
if run_timeout is None and idle_timeout is None:
return None
return cls(
run_timeout=run_timeout,
idle_timeout=idle_timeout,
refresh_on=value.refresh_on,
)
KeyFuncT = TypeVar("KeyFuncT", bound=Callable[..., str | bytes])
@@ -548,6 +627,7 @@ class PregelExecutableTask:
path: tuple[str | int | tuple, ...]
writers: Sequence[Runnable] = ()
subgraphs: Sequence[PregelProtocol] = ()
timeout: TimeoutPolicy | None = None
class StateSnapshot(NamedTuple):
@@ -587,6 +667,8 @@ class Send:
Attributes:
node (str): The name of the target node to send the message to.
arg (Any): The state or message to send to the target node.
timeout (TimeoutPolicy | None): Optional timeout policy for this specific
pushed task. If omitted, the target node's timeout policy is used.
!!! example
@@ -616,33 +698,47 @@ class Send:
```
"""
__slots__ = ("node", "arg")
__slots__ = ("node", "arg", "timeout")
node: str
arg: Any
timeout: TimeoutPolicy | None
def __init__(self, /, node: str, arg: Any) -> None:
def __init__(
self,
/,
node: str,
arg: Any,
*,
timeout: float | timedelta | TimeoutPolicy | None = None,
) -> None:
"""
Initialize a new instance of the `Send` class.
Args:
node: The name of the target node to send the message to.
arg: The state or message to send to the target node.
timeout: Optional timeout policy for this specific pushed task. A
number or `timedelta` is treated as a hard `run_timeout`.
"""
self.node = node
self.arg = arg
self.timeout = TimeoutPolicy.coerce(timeout)
def __hash__(self) -> int:
return hash((self.node, self.arg))
return hash((self.node, self.arg, self.timeout))
def __repr__(self) -> str:
return f"Send(node={self.node!r}, arg={self.arg!r})"
if self.timeout is None:
return f"Send(node={self.node!r}, arg={self.arg!r})"
return f"Send(node={self.node!r}, arg={self.arg!r}, timeout={self.timeout!r})"
def __eq__(self, value: object) -> bool:
return (
isinstance(value, Send)
and self.node == value.node
and self.arg == value.arg
and self.timeout == value.timeout
)
+5 -5
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.1.6"
version = "1.2.0a6"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
@@ -24,10 +24,10 @@ classifiers = [
'Programming Language :: Python :: 3.13',
]
dependencies = [
"langchain-core>=0.1",
"langgraph-checkpoint>=2.1.0,<5.0.0",
"langchain-core>=1.4.0a2,<2",
"langgraph-checkpoint>=4.1.0a3,<5.0.0",
"langgraph-sdk>=0.3.0,<0.4.0",
"langgraph-prebuilt>=1.0.9,<1.1.0",
"langgraph-prebuilt>=1.1.0a2,<1.2.0",
"xxhash>=3.5.0",
"pydantic>=2.7.4",
]
@@ -38,7 +38,7 @@ Homepage = "https://docs.langchain.com/oss/python/langgraph/overview"
Documentation = "https://reference.langchain.com/python/langgraph/"
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/langgraph"
Changelog = "https://github.com/langchain-ai/langgraph/releases"
Twitter = "https://x.com/LangChain"
Twitter = "https://x.com/langchain_oss"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
+590 -5
View File
@@ -1,18 +1,34 @@
import operator
from collections.abc import Sequence
from typing import Annotated
import pytest
from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage
from langgraph.checkpoint.base import DELTA_SENTINEL
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.serde.types import _DeltaSnapshot
from typing_extensions import NotRequired, TypedDict
from langgraph._internal._typing import MISSING
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.channels.delta import DeltaChannel
from langgraph.channels.last_value import LastValue
from langgraph.channels.topic import Topic
from langgraph.channels.untracked_value import UntrackedValue
from langgraph.errors import EmptyChannelError, InvalidUpdateError
from langgraph.graph import START, StateGraph
from langgraph.graph.message import _messages_delta_reducer
from langgraph.graph.state import _get_channel
from langgraph.types import Overwrite
pytestmark = pytest.mark.anyio
# ---------------------------------------------------------------------------
# Core channel primitives
# ---------------------------------------------------------------------------
def test_last_value() -> None:
channel = LastValue(int).from_checkpoint(MISSING)
assert channel.ValueType is int
@@ -95,25 +111,594 @@ def test_untracked_value() -> None:
assert channel.ValueType is dict
assert channel.UpdateType is dict
# UntrackedValue should start empty
with pytest.raises(EmptyChannelError):
channel.get()
# Should be able to update with a value
test_data = {"session": "test", "temp": "dir"}
channel.update([test_data])
assert channel.get() == test_data
# Update with new value
new_data = {"session": "updated", "temp": "newdir"}
channel.update([new_data])
assert channel.get() == new_data
# On checkpoint, UntrackedValue should return MISSING
checkpoint = channel.checkpoint()
assert checkpoint is MISSING
# Creating from checkpoint with MISSING should start empty
new_channel = UntrackedValue(dict).from_checkpoint(checkpoint)
with pytest.raises(EmptyChannelError):
new_channel.get()
# ---------------------------------------------------------------------------
# DeltaChannel — message reducer
# ---------------------------------------------------------------------------
def test_delta_channel_basic_two_steps() -> None:
ch = DeltaChannel(_messages_delta_reducer, list).from_checkpoint(MISSING)
ch.update([HumanMessage(content="hi", id="h1")])
d1 = ch.checkpoint()
assert d1 is DELTA_SENTINEL
ch.update([AIMessage(content="hello", id="a1")])
d2 = ch.checkpoint()
assert d2 is DELTA_SENTINEL
assert len(ch.get()) == 2
assert ch.get()[0].content == "hi"
assert ch.get()[1].content == "hello"
def test_delta_channel_from_checkpoint_writes_list() -> None:
"""replay_writes on a fresh channel replays through the operator."""
spec = DeltaChannel(_messages_delta_reducer, list)
ch = spec.from_checkpoint(DELTA_SENTINEL)
ch.replay_writes(
[
("t0", "messages", HumanMessage(content="hi", id="h1")),
("t1", "messages", AIMessage(content="hello", id="a1")),
("t2", "messages", HumanMessage(content="bye", id="h2")),
]
)
msgs = ch.get()
assert len(msgs) == 3
assert msgs[0].content == "hi"
assert msgs[1].content == "hello"
assert msgs[2].content == "bye"
def test_delta_channel_from_checkpoint_backwards_compat() -> None:
spec = DeltaChannel(_messages_delta_reducer, list)
old_value = [HumanMessage(content="old", id="h1")]
ch = spec.from_checkpoint(old_value)
assert ch.get() == old_value
def test_delta_channel_overwrite() -> None:
ch = DeltaChannel(_messages_delta_reducer, list).from_checkpoint(MISSING)
ch.update([HumanMessage(content="old", id="h1")])
ch.update([Overwrite([HumanMessage(content="new", id="h2")])])
d = ch.checkpoint()
assert d is DELTA_SENTINEL
assert len(ch.get()) == 1
assert ch.get()[0].content == "new"
def test_delta_channel_remove_message_and_replay() -> None:
"""RemoveMessage must round-trip correctly when writes are replayed."""
spec = DeltaChannel(_messages_delta_reducer, list)
ch = spec.from_checkpoint(MISSING)
ch.update([HumanMessage(content="hi", id="h1")])
ch.update([AIMessage(content="hello", id="a1")])
assert ch.get() == [
HumanMessage(content="hi", id="h1"),
AIMessage(content="hello", id="a1"),
]
ch.update([RemoveMessage(id="a1")])
assert ch.get() == [HumanMessage(content="hi", id="h1")]
ch2 = spec.from_checkpoint(DELTA_SENTINEL)
ch2.replay_writes(
[
("t0", "messages", HumanMessage(content="hi", id="h1")),
("t1", "messages", AIMessage(content="hello", id="a1")),
("t2", "messages", RemoveMessage(id="a1")),
]
)
assert ch2.get() == [HumanMessage(content="hi", id="h1")]
def test_delta_channel_update_by_id_and_replay() -> None:
"""Updating a message by ID must round-trip correctly through writes replay."""
spec = DeltaChannel(_messages_delta_reducer, list)
ch = spec.from_checkpoint(MISSING)
ch.update([HumanMessage(content="original", id="h1")])
ch.update([HumanMessage(content="updated", id="h1")])
assert ch.get() == [HumanMessage(content="updated", id="h1")]
ch2 = spec.from_checkpoint(DELTA_SENTINEL)
ch2.replay_writes(
[
("t0", "messages", HumanMessage(content="original", id="h1")),
("t1", "messages", HumanMessage(content="updated", id="h1")),
]
)
assert len(ch2.get()) == 1
assert ch2.get()[0].content == "updated"
def test_delta_channel_dict_coercion() -> None:
"""_messages_delta_reducer coerces dict writes to BaseMessage objects.
HTTP-driven input always arrives as JSON dicts. The reducer must coerce
them (same contract as add_messages) so graphs work without a separate
coercion step.
"""
ch = DeltaChannel(_messages_delta_reducer, list).from_checkpoint(MISSING)
# dict input — simulates what arrives from the HTTP API
ch.update([{"role": "human", "content": "hello", "id": "h1"}])
assert len(ch.get()) == 1
assert isinstance(ch.get()[0], HumanMessage)
assert ch.get()[0].content == "hello"
assert ch.get()[0].id == "h1"
# update by ID via dict
ch.update([{"role": "ai", "content": "world", "id": "h1"}])
assert len(ch.get()) == 1
assert ch.get()[0].content == "world"
# remove via RemoveMessage instance (same contract as add_messages)
ch.update([RemoveMessage(id="h1")])
assert ch.get() == []
def test_messages_delta_reducer_coerces_state() -> None:
"""State (left side) is coerced when raw — supports raw initial input
and deserialized blobs. The steady-state path (state already typed)
short-circuits and skips coercion.
"""
state = [{"role": "human", "content": "hello", "id": "h1"}]
writes = [[{"role": "ai", "content": "world", "id": "h1"}]]
result = _messages_delta_reducer(state, writes) # type: ignore[arg-type]
assert len(result) == 1
assert isinstance(result[0], AIMessage)
assert result[0].content == "world"
assert result[0].id == "h1"
def test_messages_delta_reducer_tuple_write_is_one_message() -> None:
"""A top-level tuple write is one message-like, not a sequence to flatten.
`("user", "hi")` is a valid `MessageLikeRepresentation`; flattening it
would produce two HumanMessages ("user", "hi") instead of one.
"""
result = _messages_delta_reducer([], [("user", "hi")]) # type: ignore[arg-type]
assert len(result) == 1
assert isinstance(result[0], HumanMessage)
assert result[0].content == "hi"
def test_delta_channel_checkpoint_returns_sentinel() -> None:
"""checkpoint() always returns DELTA_SENTINEL regardless of state."""
ch = DeltaChannel(_messages_delta_reducer, list).from_checkpoint(MISSING)
assert ch.checkpoint() is DELTA_SENTINEL
ch.update([HumanMessage(content="hi", id="h1")])
assert ch.checkpoint() is DELTA_SENTINEL
# ---------------------------------------------------------------------------
# DeltaChannel — snapshot frequency
# ---------------------------------------------------------------------------
def test_delta_channel_snapshot_step_based() -> None:
"""Snapshots fire on every Nth step regardless of whether the channel was written.
With snapshot_frequency=N, every Nth pregel step produces a _DeltaSnapshot
blob even if the channel had no write that step (eager snapshot). This
bounds the ancestor walk to at most N steps on any read.
"""
class State(TypedDict):
messages: Annotated[
list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=5)
]
other: str
def node_a(state: State) -> dict:
i = len(state["messages"]) // 2
return {"messages": [AIMessage(content=f"a{i}", id=f"a{i}")]}
def node_b(state: State) -> dict:
return {"other": "y"}
g = StateGraph(State)
g.add_node("a", node_a)
g.add_node("b", node_b)
g.add_edge(START, "a")
g.add_edge("a", "b")
saver = InMemorySaver()
graph = g.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "t1"}}
for i in range(6):
graph.invoke(
{"messages": [HumanMessage(content=f"h{i}", id=f"h{i}")], "other": ""},
config,
)
msg_blob_values = [
saver.serde.loads_typed((type_tag, blob))
for k, (type_tag, blob) in saver.blobs.items()
if k[2] == "messages" and type_tag == "msgpack" and blob
]
snapshots = [v for v in msg_blob_values if isinstance(v, _DeltaSnapshot)]
assert snapshots, "expected at least one _DeltaSnapshot blob for messages"
state = graph.get_state(config)
assert len(state.values["messages"]) == 12 # 6 human + 6 AI
def test_delta_channel_snapshot_fires_even_when_not_written() -> None:
"""Eager snapshot: _DeltaSnapshot stored at snapshot step even when the
channel had no write that step (node_b doesn't touch messages).
"""
class State(TypedDict):
messages: Annotated[
list, DeltaChannel(_messages_delta_reducer, snapshot_frequency=3)
]
tick: int
def writer(state: State) -> dict:
i = len(state["messages"]) // 2
return {"messages": [AIMessage(content=f"a{i}", id=f"a{i}")]}
def ticker(state: State) -> dict:
return {"tick": state["tick"] + 1}
g = StateGraph(State)
g.add_node("writer", writer)
g.add_node("ticker", ticker)
g.add_edge(START, "writer")
g.add_edge("writer", "ticker")
saver = InMemorySaver()
graph = g.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "t1"}}
for i in range(5):
graph.invoke(
{"messages": [HumanMessage(content=f"h{i}", id=f"h{i}")], "tick": 0},
config,
)
msg_blobs = {
k: saver.serde.loads_typed((t, b))
for k, (t, b) in saver.blobs.items()
if k[2] == "messages" and t == "msgpack" and b
}
snapshots = {k: v for k, v in msg_blobs.items() if isinstance(v, _DeltaSnapshot)}
assert snapshots, (
"eager snapshots must fire even on steps where messages wasn't written"
)
state = graph.get_state(config)
assert len(state.values["messages"]) == 10 # 5 human + 5 AI
# ---------------------------------------------------------------------------
# DeltaChannel — end-to-end (InMemorySaver)
# ---------------------------------------------------------------------------
def test_delta_channel_inmemory_saver_assembles_writes() -> None:
"""InMemorySaver assembles writes from checkpoint_writes inside get_tuple."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer, list)]
n = {"v": 0}
def respond(state: State) -> dict:
n["v"] += 1
return {"messages": [AIMessage(content=f"ok{n['v']}", id=f"ai{n['v']}")]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
saver = InMemorySaver()
graph = builder.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "t1"}}
graph.invoke({"messages": [HumanMessage(content="hi", id="h1")]}, config)
graph.invoke({"messages": [HumanMessage(content="bye", id="h2")]}, config)
saved = saver.get_tuple(config)
assert saved is not None
assert "messages" not in saved.checkpoint["channel_values"]
state = graph.get_state(config)
assert len(state.values["messages"]) == 4 # 2 human + 2 AI
# ---------------------------------------------------------------------------
# DeltaChannel — dict reducer
# ---------------------------------------------------------------------------
def _delta_channel_with_type(op, typ):
"""Build a DeltaChannel with an explicit type via the Annotated injection path."""
return _get_channel("_test", Annotated[typ, DeltaChannel(op)])
def test_delta_channel_dict_reducer_fresh_channel() -> None:
"""DeltaChannel with a dict reducer starts as empty dict on MISSING checkpoint."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
ch = _delta_channel_with_type(merge_dicts, dict).from_checkpoint(MISSING)
assert ch.is_available()
assert ch.get() == {}
def test_delta_channel_dict_reducer_basic_updates() -> None:
"""DeltaChannel with a dict reducer accumulates key/value pairs across steps."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
ch = _delta_channel_with_type(merge_dicts, dict).from_checkpoint(MISSING)
ch.update([{"a": 1}])
d1 = ch.checkpoint()
assert d1 is DELTA_SENTINEL
ch.update([{"b": 2}])
d2 = ch.checkpoint()
assert d2 is DELTA_SENTINEL
assert ch.get() == {"a": 1, "b": 2}
def test_delta_channel_dict_reducer_writes_reconstruction() -> None:
"""replay_writes on a fresh channel replays through a dict merge reducer."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
spec = _delta_channel_with_type(merge_dicts, dict)
ch = spec.from_checkpoint(DELTA_SENTINEL)
ch.replay_writes(
[
("t0", "files", {"a": 1}),
("t1", "files", {"b": 2}),
("t2", "files", {"c": 3}),
]
)
assert ch.get() == {"a": 1, "b": 2, "c": 3}
def test_delta_channel_dict_reducer_with_deletions() -> None:
"""Dict reducer that treats None values as deletions works end-to-end."""
def merge_files(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
for k, v in w.items():
if v is None:
result.pop(k, None)
else:
result[k] = v
return result
ch = _delta_channel_with_type(merge_files, dict).from_checkpoint(MISSING)
ch.update([{"file1.py": "content1", "file2.py": "content2"}])
ch.update([{"file1.py": None, "file3.py": "content3"}])
assert ch.get() == {"file2.py": "content2", "file3.py": "content3"}
spec = _delta_channel_with_type(merge_files, dict)
ch2 = spec.from_checkpoint(DELTA_SENTINEL)
ch2.replay_writes(
[
("t0", "files", {"file1.py": "content1", "file2.py": "content2"}),
("t1", "files", {"file1.py": None, "file3.py": "content3"}),
]
)
assert ch2.get() == {"file2.py": "content2", "file3.py": "content3"}
def test_delta_channel_dict_reducer_overwrite_in_update() -> None:
"""Overwrite(dict) in update() must preserve dict shape, not coerce to list."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
ch = _delta_channel_with_type(merge_dicts, dict).from_checkpoint(MISSING)
ch.update([{"a": 1}])
ch.update([Overwrite({"b": 2, "c": 3})])
assert ch.get() == {"b": 2, "c": 3}
def test_delta_channel_dict_reducer_overwrite_in_writes_replay() -> None:
"""Overwrite(dict) embedded in replayed writes must reconstruct as dict."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
spec = _delta_channel_with_type(merge_dicts, dict)
ch = spec.from_checkpoint(DELTA_SENTINEL)
ch.replay_writes(
[
("t0", "files", {"a": 1}),
("t1", "files", Overwrite({"x": 10, "y": 20})),
("t2", "files", {"z": 30}),
]
)
assert ch.get() == {"x": 10, "y": 20, "z": 30}
def test_delta_channel_dict_reducer_with_notrequired_annotation() -> None:
"""DeltaChannel infers dict type through `Annotated[NotRequired[dict[...]], ch]`."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
annotation = Annotated[NotRequired[dict[str, int]], DeltaChannel(merge_dicts)]
ch = _get_channel("files", annotation).from_checkpoint(MISSING)
assert ch.get() == {}
ch.update([{"a": 1}])
ch.update([{"b": 2}])
assert ch.get() == {"a": 1, "b": 2}
def test_delta_channel_dict_reducer_end_to_end_filesystem() -> None:
"""End-to-end: graph with dict-reducer (filesystem-style) channel wrapped in DeltaChannel."""
def merge_files(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
for k, v in w.items():
if v is None:
result.pop(k, None)
else:
result[k] = v
return result
class State(TypedDict):
files: Annotated[dict[str, str], DeltaChannel(merge_files)]
turn = {"v": 0}
def write_file(state: State) -> dict:
turn["v"] += 1
n = turn["v"]
return {"files": {f"/doc_{n}.txt": f"content for turn {n}"}}
builder = StateGraph(State)
builder.add_node("write_file", write_file)
builder.add_edge(START, "write_file")
saver = InMemorySaver()
graph = builder.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "fs"}}
for _ in range(3):
graph.invoke({"files": {}}, config)
saved = saver.get_tuple(config)
assert saved is not None
assert "files" not in saved.checkpoint["channel_values"]
state = graph.get_state(config)
assert state.values["files"] == {
"/doc_1.txt": "content for turn 1",
"/doc_2.txt": "content for turn 2",
"/doc_3.txt": "content for turn 3",
}
def delete_file(state: State) -> dict:
return {"files": {"/doc_1.txt": None}}
builder2 = StateGraph(State)
builder2.add_node("write_file", write_file)
builder2.add_node("delete_file", delete_file)
builder2.add_edge(START, "write_file")
builder2.add_edge("write_file", "delete_file")
turn["v"] = 0
saver2 = InMemorySaver()
graph2 = builder2.compile(checkpointer=saver2)
config2 = {"configurable": {"thread_id": "fs2"}}
graph2.invoke({"files": {}}, config2)
state2 = graph2.get_state(config2)
assert state2.values["files"] == {}
def test_delta_channel_dict_reducer_backwards_compat() -> None:
"""A pre-DeltaChannel dict checkpoint must load as a dict, not be listified."""
def merge_dicts(state: dict, writes: list) -> dict:
result = dict(state)
for w in writes:
result.update(w)
return result
spec = _delta_channel_with_type(merge_dicts, dict)
old_value = {"a": 1, "b": 2}
ch = spec.from_checkpoint(old_value)
assert ch.get() == {"a": 1, "b": 2}
# ---------------------------------------------------------------------------
# DeltaChannel — seed / pre-delta migration
# ---------------------------------------------------------------------------
def test_delta_channel_from_checkpoint_honors_seed() -> None:
"""A non-sentinel value to from_checkpoint is used as the pre-delta seed.
Guards the pre-delta migration path: when the saver's ancestor walk hits
a pre-DeltaChannel blob it passes it as `seed` so replay reconstructs
the post-migration state correctly rather than replaying from empty.
"""
spec = DeltaChannel(_messages_delta_reducer, list)
seed = [HumanMessage(content="pre-delta", id="p1")]
ch = spec.from_checkpoint(seed)
ch.replay_writes(
[
("t0", "messages", AIMessage(content="delta-1", id="d1")),
("t1", "messages", HumanMessage(content="delta-2", id="d2")),
]
)
msgs = ch.get()
assert [m.content for m in msgs] == ["pre-delta", "delta-1", "delta-2"]
def test_delta_channel_from_checkpoint_seed_without_writes() -> None:
"""Reconstruction at a pre-delta ancestor with no newer deltas returns
just the seed the saver's terminator fired immediately."""
spec = DeltaChannel(_messages_delta_reducer, list)
seed = [HumanMessage(content="only-snap", id="s1")]
ch = spec.from_checkpoint(seed)
ch.replay_writes([])
assert ch.get() == seed
def test_delta_channel_from_checkpoint_seed_none_is_distinct_from_sentinel() -> None:
"""`seed=None` must start replay from None, not from an empty channel.
The DELTA_SENTINEL / MISSING sentinels mean 'no seed'; passing `None`
explicitly should feed None to the reducer as the left operand.
"""
def replace(state, writes):
return writes[-1] if writes else state
spec = DeltaChannel(replace, list)
ch = spec.from_checkpoint(None)
ch.replay_writes([("t0", "x", "after")])
assert ch.get() == "after"
@@ -0,0 +1,478 @@
"""Benchmark: DeltaChannel snapshot_frequency — storage vs. read-depth tradeoff.
Run directly: python tests/test_delta_channel_benchmark.py
Run via pytest: pytest tests/test_delta_channel_benchmark.py -s
Part 1 baseline (original): DeltaChannel(inf) vs add_messages (BinOp).
Part 2 snapshot_frequency sweep: shows the storage/read-latency tradeoff
across frequencies [1, 5, 10, 50, inf] at scale.
Key insight:
snapshot_frequency=inf O(N) storage, O(N) read depth (pure delta)
snapshot_frequency=N O(/N) storage, O(N) read depth bounded by freq
snapshot_frequency=1 O() storage, O(1) read depth (full snapshot)
"""
from __future__ import annotations
import contextlib
import math
import os
import sys
import time
from typing import Annotated, Any
import pytest
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.checkpoint.memory import MemorySaver
from typing_extensions import TypedDict
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import END, StateGraph
from langgraph.graph.message import _messages_delta_reducer, add_messages
try:
from langgraph.checkpoint.postgres import PostgresSaver
_POSTGRES_AVAILABLE = True
_POSTGRES_URI = os.environ.get(
"LANGGRAPH_BENCH_POSTGRES_URI",
"postgres://postgres@localhost:5432/postgres?sslmode=disable",
)
except ImportError:
_POSTGRES_AVAILABLE = False
# ---------------------------------------------------------------------------
# Realistic message payload (~100 tokens / ~400 chars each)
# ---------------------------------------------------------------------------
_HUMAN_TEMPLATE = (
"I need help understanding the implications of {topic} on our system architecture. "
"Specifically, I'm concerned about how this interacts with our existing {concern} "
"and whether we need to refactor the {component} layer before proceeding. "
"We've had prior incidents in this area and want to be deliberate. "
"What should we prioritize first, and are there known failure modes we should design around from the start?"
)
_AI_TEMPLATE = (
"Great question about {topic}. The key insight here is that {concern} introduces "
"a subtle ordering dependency that most teams overlook until they hit it in production. "
"For your {component} layer specifically, I'd recommend starting with a careful audit "
"of the interface boundaries before making any structural changes. This will give you "
"a clear picture of the blast radius and let you sequence the migration safely."
)
_TOPICS = [
"distributed tracing",
"eventual consistency",
"schema migration",
"backpressure handling",
"idempotency guarantees",
"cache invalidation",
"connection pooling",
"rate limiting",
"circuit breaking",
"observability pipelines",
]
_CONCERNS = [
"concurrency model",
"retry semantics",
"state management",
"error propagation",
"latency budget",
]
_COMPONENTS = [
"persistence",
"routing",
"ingestion",
"aggregation",
"serialization",
]
def _human_content(i: int) -> str:
return _HUMAN_TEMPLATE.format(
topic=_TOPICS[i % len(_TOPICS)],
concern=_CONCERNS[i % len(_CONCERNS)],
component=_COMPONENTS[i % len(_COMPONENTS)],
)
def _ai_content(i: int) -> str:
return _AI_TEMPLATE.format(
topic=_TOPICS[i % len(_TOPICS)],
concern=_CONCERNS[i % len(_CONCERNS)],
component=_COMPONENTS[i % len(_COMPONENTS)],
)
# ---------------------------------------------------------------------------
# State definitions
# ---------------------------------------------------------------------------
class BinaryState(TypedDict):
messages: Annotated[list, add_messages]
class DeltaState(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def _make_delta_state(snapshot_frequency: int | float) -> type:
"""Create a TypedDict with DeltaChannel at the given snapshot_frequency."""
channel = DeltaChannel(
_messages_delta_reducer, snapshot_frequency=snapshot_frequency
)
# Use the functional TypedDict form so the Annotated type is stored as an
# already-evaluated object rather than a forward-reference string (which
# would fail when get_type_hints tries to resolve 'snapshot_frequency').
return TypedDict( # type: ignore[return-value]
f"DeltaState_freq{snapshot_frequency}",
{"messages": Annotated[list, channel]},
)
# ---------------------------------------------------------------------------
# Graph factory
# ---------------------------------------------------------------------------
def _make_graph(state_cls: type, checkpointer: Any = None) -> Any:
def human_node(state: Any) -> dict:
return {}
def ai_node(state: Any) -> dict:
i = len(state["messages"]) // 2
return {"messages": [AIMessage(content=_ai_content(i), id=f"a{i}")]}
g = StateGraph(state_cls)
g.add_node("human", human_node)
g.add_node("ai", ai_node)
g.add_edge("human", "ai")
g.add_edge("ai", END)
g.set_entry_point("human")
return g.compile(checkpointer=checkpointer or MemorySaver())
# ---------------------------------------------------------------------------
# Measurement helpers
# ---------------------------------------------------------------------------
def _total_blob_bytes(saver: MemorySaver) -> int:
total = 0
for (_, _, _, _), (type_tag, blob) in saver.blobs.items():
if blob is not None:
total += len(blob)
return total
def _run_turns(
n_turns: int,
state_cls: type,
checkpointer: Any = None,
) -> tuple[float, float, int]:
"""Run n_turns conversation turns.
Returns (write_elapsed_s, read_elapsed_s, total_blob_bytes).
Read latency is the average of 5 get_state calls after the full history
is built forces state rehydration including ancestor replay if needed.
"""
graph = _make_graph(state_cls, checkpointer)
config = {"configurable": {"thread_id": "bench"}}
t0 = time.perf_counter()
for i in range(n_turns):
graph.invoke(
{"messages": [HumanMessage(content=_human_content(i), id=f"h{i}")]},
config,
)
write_elapsed = time.perf_counter() - t0
t1 = time.perf_counter()
for _ in range(5):
graph.get_state(config)
read_elapsed = (time.perf_counter() - t1) / 5
blob_bytes = (
_total_blob_bytes(graph.checkpointer)
if isinstance(graph.checkpointer, MemorySaver)
else -1
)
return write_elapsed, read_elapsed, blob_bytes
def _fmt_bytes(n: int) -> str:
if n >= 1_000_000:
return f"{n / 1_000_000:.1f} MB"
if n >= 1_000:
return f"{n / 1_000:.1f} KB"
return f"{n} B"
def _approx_tokens(n_turns: int) -> str:
tokens = n_turns * 200
if tokens >= 1_000_000:
return f"~{tokens / 1_000_000:.1f}M tok"
if tokens >= 1_000:
return f"~{tokens / 1_000:.0f}K tok"
return f"~{tokens} tok"
# ---------------------------------------------------------------------------
# Checkpointer factories
# ---------------------------------------------------------------------------
@contextlib.contextmanager
def _pg_saver(thread_id: str = "bench"):
"""Context manager that yields a fresh PostgresSaver and cleans up after."""
with PostgresSaver.from_conn_string(_POSTGRES_URI) as saver:
saver.setup()
with saver._cursor() as cur:
for tbl in ("checkpoints", "checkpoint_blobs", "checkpoint_writes"):
cur.execute(f"DELETE FROM {tbl} WHERE thread_id = %s", (thread_id,))
yield saver
with saver._cursor() as cur:
for tbl in ("checkpoints", "checkpoint_blobs", "checkpoint_writes"):
cur.execute(f"DELETE FROM {tbl} WHERE thread_id = %s", (thread_id,))
def _checkpointers() -> list[tuple[str, Any]]:
"""Return (label, saver_or_None) pairs for available checkpointers."""
result: list[tuple[str, Any]] = [("InMemory", None)]
if _POSTGRES_AVAILABLE:
try:
import psycopg
psycopg.connect(_POSTGRES_URI).close()
result.append(("Postgres", "postgres"))
except Exception:
pass
return result
# ---------------------------------------------------------------------------
# Part 1: baseline DeltaChannel(inf) vs add_messages
# ---------------------------------------------------------------------------
BASELINE_TURN_COUNTS = [10, 25, 50, 100, 500]
DELTA_ONLY_TURN_COUNTS = [1000]
def _run_baseline_for_checkpointer(cp_label: str, cp_hint: Any) -> None:
W = 72
def _make_saver():
if cp_hint is None:
return contextlib.nullcontext(None)
return _pg_saver()
rows: list[tuple[int, Any, Any, Any, Any, Any, Any]] = []
for turns in BASELINE_TURN_COUNTS:
with _make_saver() as saver:
b_wt, b_rt, b_bytes = _run_turns(turns, BinaryState, saver)
with _make_saver() as saver:
d_wt, d_rt, d_bytes = _run_turns(turns, DeltaState, saver)
rows.append((turns, b_bytes, d_bytes, b_rt, d_rt, b_wt, d_wt))
for turns in DELTA_ONLY_TURN_COUNTS:
with _make_saver() as saver:
d_wt, d_rt, d_bytes = _run_turns(turns, DeltaState, saver)
rows.append((turns, None, d_bytes, None, d_rt, None, d_wt))
def _bytes_or_na(v: Any) -> str:
if v is None or v < 0:
return "n/a"
return _fmt_bytes(v)
def _ms_or_na(v: Any) -> str:
return "n/a" if v is None else f"{v * 1000:.1f}ms"
print(f"\n [{cp_label}] Storage (blob bytes)")
print(
f" {'turns':>6} {'ctx':>10} {'add_msgs':>12} {'delta(inf)':>12} {'savings':>8}"
)
print(" " + "-" * (W - 2))
for turns, b_bytes, d_bytes, b_rt, d_rt, b_wt, d_wt in rows:
if b_bytes is None or b_bytes < 0 or d_bytes is None or d_bytes < 0:
ratio_str = "n/a"
else:
ratio = b_bytes / d_bytes if d_bytes else float("inf")
ratio_str = f"{ratio:.0f}x"
print(
f" {turns:>6} {_approx_tokens(turns):>10} "
f"{_bytes_or_na(b_bytes):>12} {_bytes_or_na(d_bytes):>12} {ratio_str:>8}"
)
print(f"\n [{cp_label}] Read latency (avg of 5 get_state calls)")
print(f" {'turns':>6} {'ctx':>10} {'add_msgs':>12} {'delta(inf)':>12}")
print(" " + "-" * (W - 2))
for turns, b_bytes, d_bytes, b_rt, d_rt, b_wt, d_wt in rows:
print(
f" {turns:>6} {_approx_tokens(turns):>10} "
f"{_ms_or_na(b_rt):>12} {_ms_or_na(d_rt):>12}"
)
def run_baseline_benchmark() -> None:
print()
print("Part 1 — DeltaChannel(inf) vs add_messages: storage & latency")
print("=" * 72)
for cp_label, cp_hint in _checkpointers():
_run_baseline_for_checkpointer(cp_label, cp_hint)
print()
# ---------------------------------------------------------------------------
# Part 2: snapshot_frequency sweep
# ---------------------------------------------------------------------------
# Frequencies to test. 1 = always snapshot (like BinOp), inf = pure delta.
SNAPSHOT_FREQUENCIES: list[int | float] = [1, 5, 10, 50, math.inf]
# Turn counts for the sweep — high enough to show storage divergence.
SWEEP_TURN_COUNTS = [50, 100, 500]
def _freq_label(freq: int | float) -> str:
if freq == math.inf:
return "inf"
return str(int(freq))
def _run_sweep_for_checkpointer(cp_label: str, cp_hint: Any) -> None:
def _make_saver():
if cp_hint is None:
return contextlib.nullcontext(None)
return _pg_saver()
# Collect results: {turns: {freq_label: (write_s, read_s, bytes)}}
results: dict[int, dict[str, tuple[float, float, int]]] = {}
for turns in SWEEP_TURN_COUNTS:
results[turns] = {}
for freq in SNAPSHOT_FREQUENCIES:
state_cls = _make_delta_state(freq)
with _make_saver() as saver:
wt, rt, bb = _run_turns(turns, state_cls, saver)
results[turns][_freq_label(freq)] = (wt, rt, bb)
freq_labels = [_freq_label(f) for f in SNAPSHOT_FREQUENCIES]
col_w = 12
header = f" {'turns':>6} {'ctx':>10}" + "".join(
f" {f'freq={freq_label}':>{col_w}}" for freq_label in freq_labels
)
print(f"\n [{cp_label}] Storage (blob bytes) — lower is better")
print(header)
print(" " + "-" * (len(header) - 2))
for turns in SWEEP_TURN_COUNTS:
row = f" {turns:>6} {_approx_tokens(turns):>10}"
for label in freq_labels:
_, _, bb = results[turns][label]
row += f" {_fmt_bytes(bb) if bb >= 0 else 'n/a':>{col_w}}"
print(row)
print(f"\n [{cp_label}] Read latency (avg of 5 get_state) — lower is better")
print(header)
print(" " + "-" * (len(header) - 2))
for turns in SWEEP_TURN_COUNTS:
row = f" {turns:>6} {_approx_tokens(turns):>10}"
for label in freq_labels:
_, rt, _ = results[turns][label]
row += f" {f'{rt * 1000:.1f}ms':>{col_w}}"
print(row)
print(
f"\n [{cp_label}] Per-invoke write latency (total / turns) — lower is better"
)
print(header)
print(" " + "-" * (len(header) - 2))
for turns in SWEEP_TURN_COUNTS:
row = f" {turns:>6} {_approx_tokens(turns):>10}"
for label in freq_labels:
wt, _, _ = results[turns][label]
row += f" {f'{(wt / turns) * 1000:.1f}ms':>{col_w}}"
print(row)
def run_snapshot_freq_benchmark() -> None:
print()
print("Part 2 — DeltaChannel snapshot_frequency sweep")
print("Lower freq → fewer snapshots → less storage but deeper read replay")
print("=" * 80)
for cp_label, cp_hint in _checkpointers():
_run_sweep_for_checkpointer(cp_label, cp_hint)
print()
print("Legend:")
print(
" freq=1 snapshot every write (full blob always — same as add_messages / BinOp)"
)
print(" freq=N snapshot every N writes; read walks at most N ancestor writes")
print(" freq=inf pure delta; read walks entire ancestor chain")
print()
# ---------------------------------------------------------------------------
# Pytest entry points
# ---------------------------------------------------------------------------
@pytest.mark.skip(
reason="slow benchmark — run manually with: python tests/test_delta_channel_benchmark.py"
)
def test_delta_channel_baseline_benchmark(capsys: Any) -> None:
"""DeltaChannel(inf) uses less storage than add_messages at scale."""
with capsys.disabled():
run_baseline_benchmark()
for turns in [25, 50]:
_, _, b_bytes = _run_turns(turns, BinaryState)
_, _, d_bytes = _run_turns(turns, DeltaState)
assert d_bytes < b_bytes, (
f"DeltaChannel should use less storage at {turns} turns, "
f"got delta={d_bytes} binary={b_bytes}"
)
@pytest.mark.skip(
reason="slow benchmark — run manually with: python tests/test_delta_channel_benchmark.py"
)
def test_snapshot_freq_benchmark(capsys: Any) -> None:
"""snapshot_frequency trades storage for bounded read depth."""
with capsys.disabled():
run_snapshot_freq_benchmark()
# Correctness: results at all frequencies should agree on final state.
n_turns = 20
states: dict[str, list] = {}
for freq in SNAPSHOT_FREQUENCIES:
state_cls = _make_delta_state(freq)
graph = _make_graph(state_cls)
config = {"configurable": {"thread_id": "correctness"}}
for i in range(n_turns):
graph.invoke(
{"messages": [HumanMessage(content=_human_content(i), id=f"h{i}")]},
config,
)
state = graph.get_state(config)
states[_freq_label(freq)] = [m.id for m in state.values["messages"]]
ref = states["inf"]
for label, msg_ids in states.items():
assert msg_ids == ref, (
f"freq={label} produced different message IDs than freq=inf"
)
# ---------------------------------------------------------------------------
# Script entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
run_baseline_benchmark()
run_snapshot_freq_benchmark()
sys.exit(0)
@@ -0,0 +1,613 @@
"""Tests for the BinaryOperatorAggregate -> DeltaChannel migration path.
A thread written under `BinaryOperatorAggregate(...)` must keep working
after its annotation is swapped to `DeltaChannel(...)` on the same
checkpointer pre-migration state visible at each *settled* ancestor
checkpoint is preserved, and post-migration writes fold on top through
the reducer.
Mechanism under test: the saver's `_get_channel_writes_history(config,
channel)` walks the parent chain; when it encounters an ancestor whose
`channel_values[channel]` is a real value (not `DELTA_SENTINEL`), it
returns that as the `seed`. `DeltaChannel.from_checkpoint(seed)` uses
it as the base value, and `replay_writes(writes)` folds on-path deltas.
Scenarios covered:
1. **Basic migration (sync + async)**: build pre-migration state with
`BinaryOperatorAggregate`, swap the annotation to `DeltaChannel` on
the same checkpointer, and verify that every settled pre-migration
super-step boundary (`next=('__start__',)`) round-trips exactly
under the delta-channel view.
2. **Time travel into a pre-migration checkpoint** after migration
`graph.get_state(pre_migration_config)` at a settled ancestor
returns the same state as under the binop channel.
3. **Continuing a migrated thread**: driving one more super-step after
migration produces a state that includes the pre-migration settled
prefix plus the new delta write proving `from_checkpoint(seed)` +
`replay_writes` correctly fold post-migration deltas onto the
pre-migration seed.
4. **Base-saver fallback path**: a third-party-style subclass that
removes the optimized `InMemorySaver` override and falls back to
`BaseCheckpointSaver._get_channel_writes_history` must produce the
same result as the optimized path.
5. **Channel-type isolation across threads**: two threads on the same
checkpointer under the delta-channel graph one freshly-started,
one migrated from pre-migration state don't cross-contaminate.
The parent-chain walk is scoped to the thread.
TODO: add postgres variants in the existing `libs/checkpoint-postgres`
test files (different fixture setup; not this file).
"""
from __future__ import annotations
import operator
from typing import Annotated, Any
import pytest
from langchain_core.messages import AIMessage, HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
from typing_extensions import TypedDict
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.channels.delta import DeltaChannel
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import _messages_delta_reducer, add_messages
pytestmark = pytest.mark.anyio
# ---------------------------------------------------------------------------
# Graph factories
#
# A minimal reducer (`operator.add` on lists of str) with a noop node keeps
# state change localized to the HumanMessage-like payload passed through
# `invoke`. That isolates the pre/post-migration parity assertions to
# channel-hydration semantics.
# ---------------------------------------------------------------------------
def _noop(_state: Any) -> dict:
return {}
def _list_concat(state: list, writes: list) -> list:
result = list(state)
for w in writes:
result.extend(w if isinstance(w, list) else [w])
return result
def _binop_graph(checkpointer: Any) -> Any:
class BinopState(TypedDict):
items: Annotated[list, BinaryOperatorAggregate(list, operator.add)]
return (
StateGraph(BinopState)
.add_node("noop", _noop)
.add_edge(START, "noop")
.add_edge("noop", END)
.compile(checkpointer=checkpointer)
)
def _delta_graph(checkpointer: Any) -> Any:
class DeltaState(TypedDict):
items: Annotated[list, DeltaChannel(_list_concat)]
return (
StateGraph(DeltaState)
.add_node("noop", _noop)
.add_edge(START, "noop")
.add_edge("noop", END)
.compile(checkpointer=checkpointer)
)
def _drive(graph: Any, config: dict, tag: str, n: int) -> None:
for i in range(n):
graph.invoke({"items": [f"{tag}{i}"]}, config)
async def _adrive(graph: Any, config: dict, tag: str, n: int) -> None:
for i in range(n):
await graph.ainvoke({"items": [f"{tag}{i}"]}, config)
def _settled_boundaries(history: list) -> list[tuple[dict, list]]:
"""Return `[(config, items), ...]` for every checkpoint in `history`
whose `next == ('__start__',)` the stable boundaries between invokes.
"""
return [
(s.config, list(s.values.get("items", [])))
for s in history
if s.next == ("__start__",)
]
# ---------------------------------------------------------------------------
# 1. Basic migration (sync + async)
# ---------------------------------------------------------------------------
def test_basic_migration_preserves_pre_migration_state() -> None:
"""Build state under `BinaryOperatorAggregate`, migrate to
`DeltaChannel` on the same checkpointer, and verify that every
settled pre-migration super-step boundary round-trips exactly.
Settled boundaries (`next=('__start__',)`) are the stable hydration
targets for the migration path: writes that produced the NEXT
super-step are kept as `pending_writes` on the ancestor, so walking
from a descendant finds the ancestor's blob as the seed and
reconstructs the correct state.
"""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "basic-sync"}}
# Pre-migration: accumulate items across 3 invokes.
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 3)
pre_boundaries = _settled_boundaries(list(binop.get_state_history(config)))
assert len(pre_boundaries) >= 2, "expected multiple settled boundaries"
# Migrate: swap the annotation on the same checkpointer.
delta = _delta_graph(checkpointer)
for cfg, items in pre_boundaries:
snap = delta.get_state(cfg)
assert list(snap.values.get("items", [])) == items, (
f"snapshot mismatch at {cfg['configurable']['checkpoint_id']}: "
f"expected {items}, got {snap.values.get('items', [])}"
)
async def test_basic_migration_preserves_pre_migration_state_async() -> None:
"""Async variant of the basic migration scenario."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "basic-async"}}
binop = _binop_graph(checkpointer)
await _adrive(binop, config, "u", 3)
pre_history = [s async for s in binop.aget_state_history(config)]
pre_boundaries = _settled_boundaries(pre_history)
assert len(pre_boundaries) >= 2
delta = _delta_graph(checkpointer)
for cfg, items in pre_boundaries:
snap = await delta.aget_state(cfg)
assert list(snap.values.get("items", [])) == items, (
f"async snapshot mismatch at {cfg['configurable']['checkpoint_id']}"
)
# ---------------------------------------------------------------------------
# 2. Time travel into a pre-migration checkpoint after migration
# ---------------------------------------------------------------------------
def test_time_travel_into_pre_migration_checkpoint() -> None:
"""After migration, `graph.get_state(pre_migration_config)` at a
settled ancestor returns the state as stored at that point."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "time-travel"}}
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 3)
pre_boundaries = _settled_boundaries(list(binop.get_state_history(config)))
assert pre_boundaries, "no settled ancestors to time-travel to"
delta = _delta_graph(checkpointer)
# Pick the oldest non-empty boundary — a long distance to walk back.
non_empty = [(cfg, items) for cfg, items in pre_boundaries if items]
assert non_empty, "expected at least one non-empty boundary"
target_cfg, expected_items = non_empty[-1]
snap = delta.get_state(target_cfg)
assert list(snap.values.get("items", [])) == expected_items
# ---------------------------------------------------------------------------
# 3. Continuing a migrated thread: deltas fold onto pre-migration seed
# ---------------------------------------------------------------------------
def test_continuing_migrated_thread_folds_deltas_on_seed() -> None:
"""Resume a pre-migration settled ancestor via `invoke(None, cfg)`
under the delta-channel graph. Since the pre-migration checkpoint
has an existing `pending_writes` entry (the input for the NEXT
super-step), re-running from that ancestor reproduces the same
post-ancestor state as the original binop run.
This proves the seed-terminator + write-replay pipeline works
end-to-end across the migration boundary.
"""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "continue"}}
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 2)
# Pick the oldest settled boundary with non-empty state.
pre_boundaries = _settled_boundaries(list(binop.get_state_history(config)))
target_cfg, seed_items = next(
(cfg, items) for cfg, items in reversed(pre_boundaries) if items
)
assert seed_items, "need a non-empty seed boundary"
# Migrate and resume from the pre-migration ancestor. `invoke(None,
# cfg)` replays the pending writes staged at `cfg` under the new
# channel; the reducer folds those deltas onto the seed.
delta = _delta_graph(checkpointer)
result = delta.invoke(None, target_cfg)
# The resumed state must include the pre-migration seed items in order.
result_items = list(result.get("items", []))
for idx, prefix_item in enumerate(seed_items):
assert result_items[idx] == prefix_item, (
f"pre-migration seed item at {idx} not preserved: "
f"got {result_items[: idx + 1]}, expected {seed_items}"
)
# ---------------------------------------------------------------------------
# 4. Base-saver fallback path
# ---------------------------------------------------------------------------
class _ThirdPartyStyleSaver(InMemorySaver):
"""Simulates a third-party saver that inherits the reference
`_get_channel_writes_history` implementation from
`BaseCheckpointSaver` rather than overriding it.
We rebind the two methods to the base-class versions (via MRO) so
the fallback path is exercised even though the storage layer is
still the in-memory one.
"""
# MRO: [_ThirdPartyStyleSaver, InMemorySaver, BaseCheckpointSaver, ...]
_get_channel_writes_history = ( # type: ignore[assignment]
InMemorySaver.__mro__[1]._get_channel_writes_history # type: ignore[attr-defined]
)
_aget_channel_writes_history = ( # type: ignore[assignment]
InMemorySaver.__mro__[1]._aget_channel_writes_history # type: ignore[attr-defined]
)
def test_base_saver_fallback_matches_optimized_override() -> None:
"""The reference `BaseCheckpointSaver` implementation must produce
the same migration behavior as the optimized `InMemorySaver`
override. We drive the same migration scenario through both savers
and assert per-snapshot parity in the delta-channel view."""
# Fast path: optimized InMemorySaver override.
fast_saver = InMemorySaver()
fast_config = {"configurable": {"thread_id": "fast"}}
fast_binop = _binop_graph(fast_saver)
_drive(fast_binop, fast_config, "u", 3)
fast_delta = _delta_graph(fast_saver)
fast_history = [
(s.next, list(s.values.get("items", [])))
for s in fast_delta.get_state_history(fast_config)
]
# Slow path: base-class fallback.
slow_saver = _ThirdPartyStyleSaver()
slow_config = {"configurable": {"thread_id": "slow"}}
slow_binop = _binop_graph(slow_saver)
_drive(slow_binop, slow_config, "u", 3)
slow_delta = _delta_graph(slow_saver)
slow_history = [
(s.next, list(s.values.get("items", [])))
for s in slow_delta.get_state_history(slow_config)
]
assert slow_history == fast_history, (
"base-saver fallback should match optimized-override behavior; "
f"fast={fast_history}, slow={slow_history}"
)
# ---------------------------------------------------------------------------
# 5. Thread isolation under mixed-generation storage
# ---------------------------------------------------------------------------
def test_delta_and_migrated_threads_do_not_cross_contaminate() -> None:
"""Two threads sharing a checkpointer — one migrated from
pre-migration state, one freshly-started under DeltaChannel must
maintain independent state. The parent-chain walk in
`_get_channel_writes_history` must be scoped to the target thread.
"""
checkpointer = InMemorySaver()
migrated_cfg = {"configurable": {"thread_id": "migrated"}}
fresh_cfg = {"configurable": {"thread_id": "fresh"}}
# Thread A: pre-migration build-up.
binop = _binop_graph(checkpointer)
_drive(binop, migrated_cfg, "m", 2)
# Thread B: fresh delta-channel run.
delta = _delta_graph(checkpointer)
_drive(delta, fresh_cfg, "f", 2)
# Thread A: migrate and confirm its state is anchored in its own
# thread's pre-migration history (tag 'm'), never mixing in tag 'f'.
migrated_boundaries = _settled_boundaries(
list(delta.get_state_history(migrated_cfg))
)
assert migrated_boundaries, "migrated thread has no settled boundaries"
for _, items in migrated_boundaries:
for it in items:
assert it.startswith("m"), (
f"migrated thread leaked item from other thread: {it}"
)
# Thread B: settled boundaries must only contain 'f' tags.
fresh_boundaries = _settled_boundaries(list(delta.get_state_history(fresh_cfg)))
assert fresh_boundaries, "fresh thread has no settled boundaries"
for _, items in fresh_boundaries:
for it in items:
assert it.startswith("f"), (
f"fresh thread leaked item from migrated thread: {it}"
)
# ---------------------------------------------------------------------------
# 6. Tip-of-pre-migration hydration: the latest checkpoint from a binop-run
# thread has a real accumulated value in its own `channel_values["items"]`.
# When hydrated under the delta-channel graph via `get_state(config)` with no
# `checkpoint_id`, the short-circuit must use that value directly instead of
# walking ancestors (which would skip the tip's own blob).
# ---------------------------------------------------------------------------
def test_tip_of_pre_migration_hydrates_directly() -> None:
"""`graph.get_state(config)` at the latest (pre-migration) checkpoint
returns the full accumulated list stored in that checkpoint's own
`channel_values`. The hydration must not walk ancestors past it."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "tip-sync"}}
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 3)
binop_tip = binop.get_state(config)
expected_items = list(binop_tip.values.get("items", []))
assert expected_items == ["u0", "u1", "u2"], (
f"sanity: pre-migration tip should accumulate all 3 items, got {expected_items}"
)
delta = _delta_graph(checkpointer)
snap = delta.get_state(config)
assert list(snap.values.get("items", [])) == expected_items, (
f"tip hydration mismatch: expected {expected_items}, "
f"got {snap.values.get('items', [])}"
)
async def test_tip_of_pre_migration_hydrates_directly_async() -> None:
"""Async variant of the tip-of-pre-migration hydration scenario."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "tip-async"}}
binop = _binop_graph(checkpointer)
await _adrive(binop, config, "u", 3)
binop_tip = await binop.aget_state(config)
expected_items = list(binop_tip.values.get("items", []))
assert expected_items == ["u0", "u1", "u2"]
delta = _delta_graph(checkpointer)
snap = await delta.aget_state(config)
assert list(snap.values.get("items", [])) == expected_items, (
f"async tip hydration mismatch: expected {expected_items}, "
f"got {snap.values.get('items', [])}"
)
# ---------------------------------------------------------------------------
# 7. `update_state` after migration writes a real value to the new
# checkpoint's `channel_values` (not a sentinel). Hydration must use it
# directly — the ancestor walk would skip this blob and return stale state.
# ---------------------------------------------------------------------------
def test_update_state_after_migration_uses_written_value() -> None:
"""After migrating and running at least one post-migration super-step
(so the thread's tip has a `DELTA_SENTINEL`), `update_state` writes a
concrete value to a new checkpoint's `channel_values`. `get_state`
must reflect that concrete value."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "update-state"}}
# Pre-migration: accumulate a little state.
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 2)
# Migrate and run one more super-step so the tip is a post-migration
# checkpoint with `DELTA_SENTINEL` in its own `channel_values`.
delta = _delta_graph(checkpointer)
delta.invoke({"items": ["post"]}, config)
# `update_state` writes a concrete value into a new checkpoint's blob
# via the reducer against the hydrated prior state.
delta.update_state(config, {"items": ["x", "y"]})
snap = delta.get_state(config)
updated_items = list(snap.values.get("items", []))
# Must include the "x","y" update; without the hydration fix, the
# update_state-written blob would be skipped in favor of an ancestor
# walk, and the update values would disappear.
assert "x" in updated_items and "y" in updated_items, (
f"update_state values missing from snapshot: {updated_items}"
)
# The "x","y" items should be folded onto the prior accumulated state,
# not stand alone. This verifies the update-written blob is used
# directly by `get_state` (no ancestor walk past it).
assert len(updated_items) >= 4, (
f"update_state snapshot should preserve pre-update state, got {updated_items}"
)
assert updated_items[-2:] == ["x", "y"], (
f"update_state deltas should be at the tail, got {updated_items}"
)
# ---------------------------------------------------------------------------
# 8. Fork from an `update_state` checkpoint: a new run branched off the
# update_state-produced checkpoint must see that checkpoint's concrete
# `channel_values` as its base, with new deltas folded on top.
# ---------------------------------------------------------------------------
def test_fork_from_update_state_checkpoint() -> None:
"""Branching a new run from the checkpoint produced by `update_state`
must use that checkpoint's concrete blob as the base. Additional
deltas from the forked run fold onto it through the reducer."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "fork"}}
# Pre-migration build-up, then migrate and add one post-migration step.
binop = _binop_graph(checkpointer)
_drive(binop, config, "u", 2)
delta = _delta_graph(checkpointer)
delta.invoke({"items": ["post"]}, config)
# Apply `update_state` and capture the returned config (references
# the new checkpoint produced by the update).
update_cfg = delta.update_state(config, {"items": ["x", "y"]})
update_snap = delta.get_state(update_cfg)
base_items = list(update_snap.values.get("items", []))
assert "x" in base_items and "y" in base_items, (
f"update_state values missing from snapshot: {base_items}"
)
assert base_items[-2:] == ["x", "y"], (
f"sanity: update_state deltas should be at the tail, got {base_items}"
)
# Fork: invoke from the update_state checkpoint with a new delta.
forked = delta.invoke({"items": ["fork0"]}, update_cfg)
forked_items = list(forked.get("items", []))
# The fork must see the update_state-written blob as its base (not
# walk past it), and the new delta must fold on top of it.
assert forked_items[: len(base_items)] == base_items, (
f"fork lost update_state base: base={base_items}, forked={forked_items}"
)
assert forked_items[-1] == "fork0", f"fork delta not appended: {forked_items}"
# ---------------------------------------------------------------------------
# 9. Migration from `add_messages` → `DeltaChannel(_messages_delta_reducer)`
#
# `add_messages` is the primary real-world use case: it creates a
# BinaryOperatorAggregate with dedup-by-ID and RemoveMessage semantics.
# After swapping the annotation to DeltaChannel, pre-migration blobs
# (plain lists of Message objects) must be used directly as the seed.
# ---------------------------------------------------------------------------
def _add_messages_graph(checkpointer: Any) -> Any:
class MessagesState(TypedDict):
messages: Annotated[list, add_messages]
return (
StateGraph(MessagesState)
.add_node("noop", _noop)
.add_edge(START, "noop")
.add_edge("noop", END)
.compile(checkpointer=checkpointer)
)
def _delta_messages_graph(checkpointer: Any) -> Any:
class DeltaMessagesState(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
return (
StateGraph(DeltaMessagesState)
.add_node("noop", _noop)
.add_edge(START, "noop")
.add_edge("noop", END)
.compile(checkpointer=checkpointer)
)
def test_add_messages_to_delta_migration_preserves_message_history() -> None:
"""Migration from `add_messages` to `DeltaChannel(_messages_delta_reducer)`
preserves message ordering and IDs at both the tip and settled ancestor
boundaries.
The pre-migration blob is a plain list of Message objects; DeltaChannel
must use it directly as the seed without walking ancestors past it.
"""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "add-messages-migration"}}
pre_graph = _add_messages_graph(checkpointer)
pre_graph.invoke({"messages": [HumanMessage(content="hello", id="h1")]}, config)
pre_graph.invoke({"messages": [AIMessage(content="hi", id="a1")]}, config)
pre_graph.invoke({"messages": [HumanMessage(content="thanks", id="h2")]}, config)
pre_tip = pre_graph.get_state(config)
assert [m.id for m in pre_tip.values["messages"]] == ["h1", "a1", "h2"]
delta_graph = _delta_messages_graph(checkpointer)
# Tip: latest checkpoint has a full list blob — must use it directly.
snap = delta_graph.get_state(config)
assert [m.id for m in snap.values["messages"]] == ["h1", "a1", "h2"], (
f"tip hydration mismatch: got {[m.id for m in snap.values['messages']]}"
)
# Settled ancestor boundaries must also match.
pre_settled = [
[m.id for m in s.values.get("messages", [])]
for s in pre_graph.get_state_history(config)
if s.next == ("__start__",)
]
delta_settled = [
[m.id for m in s.values.get("messages", [])]
for s in delta_graph.get_state_history(config)
if s.next == ("__start__",)
]
assert delta_settled == pre_settled, (
f"settled boundary mismatch after migration: "
f"pre={pre_settled}, delta={delta_settled}"
)
async def test_add_messages_to_delta_migration_preserves_message_history_async() -> (
None
):
"""Async variant of the add_messages migration test."""
checkpointer = InMemorySaver()
config = {"configurable": {"thread_id": "add-messages-migration-async"}}
pre_graph = _add_messages_graph(checkpointer)
await pre_graph.ainvoke(
{"messages": [HumanMessage(content="hello", id="h1")]}, config
)
await pre_graph.ainvoke({"messages": [AIMessage(content="hi", id="a1")]}, config)
delta_graph = _delta_messages_graph(checkpointer)
snap = await delta_graph.aget_state(config)
assert [m.id for m in snap.values["messages"]] == ["h1", "a1"], (
f"async tip hydration mismatch: got {[m.id for m in snap.values['messages']]}"
)
@@ -0,0 +1,344 @@
from __future__ import annotations
import sys
from typing import Any
import pytest
from langchain_core.callbacks.base import BaseCallbackHandler
from langchain_core.callbacks.manager import CallbackManager
from langgraph.checkpoint.memory import InMemorySaver
from typing_extensions import TypedDict
from langgraph.callbacks import (
GraphCallbackHandler,
GraphInterruptEvent,
GraphResumeEvent,
)
from langgraph.graph import START, StateGraph
from langgraph.types import Command, Interrupt, interrupt
NEEDS_CONTEXTVARS = pytest.mark.skipif(
sys.version_info < (3, 11),
reason="Python 3.11+ is required for async contextvars support",
)
class _GraphEventHandler(GraphCallbackHandler):
def __init__(self) -> None:
self.interrupt_events: list[GraphInterruptEvent] = []
self.resume_events: list[GraphResumeEvent] = []
def on_interrupt(self, event: GraphInterruptEvent) -> Any:
self.interrupt_events.append(event)
def on_resume(self, event: GraphResumeEvent) -> Any:
self.resume_events.append(event)
class _LangChainCustomEventHandler(BaseCallbackHandler):
run_inline = True
def __init__(self) -> None:
self.events: list[str] = []
def on_custom_event(self, name: str, data: Any, **kwargs: Any) -> Any:
self.events.append(name)
class _RaisingGraphEventHandler(GraphCallbackHandler):
def __init__(
self,
*,
raise_on_interrupt: bool = False,
raise_on_resume: bool = False,
raise_error: bool = False,
) -> None:
self.raise_on_interrupt = raise_on_interrupt
self.raise_on_resume = raise_on_resume
self.raise_error = raise_error
def on_interrupt(self, event: GraphInterruptEvent) -> Any:
if self.raise_on_interrupt:
raise ValueError("boom-interrupt")
def on_resume(self, event: GraphResumeEvent) -> Any:
if self.raise_on_resume:
raise ValueError("boom-resume")
class _AsyncRaisingGraphEventHandler(GraphCallbackHandler):
def __init__(
self,
*,
raise_on_interrupt: bool = False,
raise_on_resume: bool = False,
raise_error: bool = False,
) -> None:
self.raise_on_interrupt = raise_on_interrupt
self.raise_on_resume = raise_on_resume
self.raise_error = raise_error
async def on_interrupt(self, event: GraphInterruptEvent) -> Any:
if self.raise_on_interrupt:
raise ValueError("boom-interrupt")
async def on_resume(self, event: GraphResumeEvent) -> Any:
if self.raise_on_resume:
raise ValueError("boom-resume")
class _State(TypedDict):
answer: str | None
def _build_interrupt_graph() -> Any:
def ask(state: _State) -> _State:
answer = interrupt("Provide value")
return {"answer": answer}
builder = StateGraph(_State)
builder.add_node("ask", ask)
builder.add_edge(START, "ask")
return builder.compile(checkpointer=InMemorySaver())
def test_graph_callbacks_interrupt_and_resume_sync() -> None:
graph = _build_interrupt_graph()
handler = _GraphEventHandler()
langchain_handler = _LangChainCustomEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-sync"},
"callbacks": [langchain_handler, handler],
}
first = graph.invoke({"answer": None}, config)
assert "__interrupt__" in first
assert len(handler.interrupt_events) == 1
assert handler.interrupt_events[0].interrupts
assert isinstance(handler.interrupt_events[0].interrupts[0], Interrupt)
assert handler.interrupt_events[0].checkpoint_ns == ()
assert langchain_handler.events == []
handler.resume_events.clear()
resumed = graph.invoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(handler.resume_events) == 1
assert handler.resume_events[0].checkpoint_ns == ()
assert langchain_handler.events == []
@pytest.mark.anyio
@NEEDS_CONTEXTVARS
async def test_graph_callbacks_interrupt_and_resume_async() -> None:
graph = _build_interrupt_graph()
handler = _GraphEventHandler()
langchain_handler = _LangChainCustomEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-async"},
"callbacks": [langchain_handler, handler],
}
first = await graph.ainvoke({"answer": None}, config)
assert "__interrupt__" in first
assert len(handler.interrupt_events) == 1
assert handler.interrupt_events[0].interrupts
assert isinstance(handler.interrupt_events[0].interrupts[0], Interrupt)
assert handler.interrupt_events[0].checkpoint_ns == ()
assert langchain_handler.events == []
handler.resume_events.clear()
resumed = await graph.ainvoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(handler.resume_events) == 1
assert handler.resume_events[0].checkpoint_ns == ()
assert langchain_handler.events == []
def test_graph_callbacks_continue_when_interrupt_handler_raises_sync() -> None:
graph = _build_interrupt_graph()
raising_handler = _RaisingGraphEventHandler(raise_on_interrupt=True)
recording_handler = _GraphEventHandler()
first = graph.invoke(
{"answer": None},
{
"configurable": {"thread_id": "graph-callback-sync-raises"},
"callbacks": [raising_handler, recording_handler],
},
)
assert "__interrupt__" in first
assert len(recording_handler.interrupt_events) == 1
def test_graph_callbacks_continue_when_resume_handler_raises_sync() -> None:
graph = _build_interrupt_graph()
raising_handler = _RaisingGraphEventHandler(raise_on_resume=True)
recording_handler = _GraphEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-sync-raises-resume"},
"callbacks": [raising_handler, recording_handler],
}
first = graph.invoke({"answer": None}, config)
assert "__interrupt__" in first
resumed = graph.invoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(recording_handler.resume_events) == 1
def test_graph_callbacks_raise_error_propagates_sync() -> None:
graph = _build_interrupt_graph()
raising_handler = _RaisingGraphEventHandler(
raise_on_interrupt=True,
raise_error=True,
)
with pytest.raises(ValueError, match="boom-interrupt"):
graph.invoke(
{"answer": None},
{
"configurable": {"thread_id": "graph-callback-sync-raise-error"},
"callbacks": [raising_handler],
},
)
@pytest.mark.anyio
@NEEDS_CONTEXTVARS
async def test_graph_callbacks_continue_when_handler_raises_async() -> None:
graph = _build_interrupt_graph()
raising_interrupt_handler = _AsyncRaisingGraphEventHandler(raise_on_interrupt=True)
recording_handler = _GraphEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-async-raises-interrupt"},
"callbacks": [raising_interrupt_handler, recording_handler],
}
first = await graph.ainvoke({"answer": None}, config)
assert "__interrupt__" in first
assert len(recording_handler.interrupt_events) == 1
graph = _build_interrupt_graph()
raising_resume_handler = _AsyncRaisingGraphEventHandler(raise_on_resume=True)
recording_handler = _GraphEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-async-raises-resume"},
"callbacks": [raising_resume_handler, recording_handler],
}
first = await graph.ainvoke({"answer": None}, config)
assert "__interrupt__" in first
resumed = await graph.ainvoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(recording_handler.resume_events) == 1
@pytest.mark.anyio
@NEEDS_CONTEXTVARS
async def test_graph_callbacks_raise_error_propagates_async() -> None:
graph = _build_interrupt_graph()
raising_handler = _AsyncRaisingGraphEventHandler(
raise_on_interrupt=True,
raise_error=True,
)
with pytest.raises(ValueError, match="boom-interrupt"):
await graph.ainvoke(
{"answer": None},
{
"configurable": {"thread_id": "graph-callback-async-raise-error"},
"callbacks": [raising_handler],
},
)
def test_graph_callbacks_accept_base_callback_manager() -> None:
graph = _build_interrupt_graph()
graph_handler = _GraphEventHandler()
custom_handler = _LangChainCustomEventHandler()
manager = CallbackManager.configure(inheritable_callbacks=[custom_handler])
manager.add_handler(graph_handler)
first = graph.invoke(
{"answer": None},
{
"configurable": {"thread_id": "graph-callback-base-manager"},
"callbacks": manager,
},
)
assert "__interrupt__" in first
assert len(graph_handler.interrupt_events) == 1
def test_non_graph_handler_via_add_handler_does_not_crash() -> None:
"""Non-GraphCallbackHandler added via add_handler should not raise.
Libraries like opentelemetry-instrumentation-langchain monkey-patch
BaseCallbackManager.__init__ and inject handlers via add_handler().
These handlers inherit from BaseCallbackHandler, not
GraphCallbackHandler. They must be silently accepted graph lifecycle
events will simply not be dispatched to them.
"""
from langgraph.callbacks import _GraphCallbackManager
manager = _GraphCallbackManager()
plain_handler = _LangChainCustomEventHandler()
manager.add_handler(plain_handler, inherit=True)
assert plain_handler in manager.handlers
def test_non_graph_handler_does_not_receive_lifecycle_events() -> None:
"""Non-GraphCallbackHandler added alongside a GraphCallbackHandler
should not interfere with lifecycle event dispatch."""
graph = _build_interrupt_graph()
graph_handler = _GraphEventHandler()
plain_handler = _LangChainCustomEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-mixed-handlers"},
"callbacks": [plain_handler, graph_handler],
}
first = graph.invoke({"answer": None}, config)
assert "__interrupt__" in first
assert len(graph_handler.interrupt_events) == 1
assert plain_handler.events == []
resumed = graph.invoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(graph_handler.resume_events) == 1
assert plain_handler.events == []
@pytest.mark.anyio
@NEEDS_CONTEXTVARS
async def test_non_graph_handler_does_not_receive_lifecycle_events_async() -> None:
"""Async variant: non-GraphCallbackHandler should not interfere."""
graph = _build_interrupt_graph()
graph_handler = _GraphEventHandler()
plain_handler = _LangChainCustomEventHandler()
config = {
"configurable": {"thread_id": "graph-callback-mixed-handlers-async"},
"callbacks": [plain_handler, graph_handler],
}
first = await graph.ainvoke({"answer": None}, config)
assert "__interrupt__" in first
assert len(graph_handler.interrupt_events) == 1
assert plain_handler.events == []
resumed = await graph.ainvoke(Command(resume="done"), config)
assert resumed == {"answer": "done"}
assert len(graph_handler.resume_events) == 1
assert plain_handler.events == []
@@ -0,0 +1,359 @@
"""Tests for arrival-ordered interleave and push stamps."""
from __future__ import annotations
import operator
from typing import Annotated, Any
import pytest
from typing_extensions import TypedDict
from langgraph.constants import END, START
from langgraph.graph import StateGraph
from langgraph.stream import StreamChannel, StreamTransformer
from langgraph.stream._mux import StreamMux
from langgraph.stream._types import ProtocolEvent
from langgraph.stream.run_stream import GraphRunStream
from langgraph.stream.transformers import ValuesTransformer
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
class _TwoChannelTransformer(StreamTransformer):
"""Transformer that exposes two named channels for testing interleave."""
_native = True
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._alpha: StreamChannel[str] = StreamChannel("alpha")
self._beta: StreamChannel[str] = StreamChannel("beta")
def init(self) -> dict[str, Any]:
return {"alpha": self._alpha, "beta": self._beta}
def process(self, event: ProtocolEvent) -> bool:
return True
class SimpleState(TypedDict):
value: str
items: Annotated[list[str], operator.add]
def _build_simple_graph():
def node_a(state: SimpleState) -> dict:
return {"value": state["value"] + "A", "items": ["a"]}
def node_b(state: SimpleState) -> dict:
return {"value": state["value"] + "B", "items": ["b"]}
builder = StateGraph(SimpleState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
return builder.compile()
# ---------------------------------------------------------------------------
# Unit tests: push stamps on StreamChannel
# ---------------------------------------------------------------------------
class TestPushStamps:
def test_stamps_are_monotonic_across_channels(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
alpha._subscribed = True
beta._subscribed = True
alpha.push("a1")
beta.push("b1")
alpha.push("a2")
beta.push("b2")
all_stamped = list(alpha._items) + list(beta._items)
stamps = [s for s, _ in all_stamped]
assert len(set(stamps)) == 4
items_by_arrival = [item for _, item in sorted(all_stamped)]
assert items_by_arrival == ["a1", "b1", "a2", "b2"]
def test_regular_iter_strips_stamps(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
it = iter(alpha)
alpha.push("a1")
alpha.push("a2")
alpha.close()
items = list(it)
assert items == ["a1", "a2"]
assert all(isinstance(item, str) for item in items)
def test_events_channel_gets_real_stamps(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
alpha._subscribed = True
alpha.push("a1")
mux._events._subscribed = True
mux._events.push({"method": "test", "data": "x"})
alpha.push("a2")
all_stamps = [s for s, _ in alpha._items] + [s for s, _ in mux._events._items]
assert len(set(all_stamps)) == len(all_stamps), "all stamps should be unique"
assert all(s > 0 for s in all_stamps), "no stamp should be zero"
def test_channel_without_mux_gets_zero_stamp(self) -> None:
ch: StreamChannel[str] = StreamChannel()
ch._bind(is_async=False)
ch._subscribed = True
ch.push("x")
assert list(ch._items) == [(0, "x")]
# ---------------------------------------------------------------------------
# Unit tests: interleave arrival order
# ---------------------------------------------------------------------------
class TestInterleaveArrivalOrder:
def test_arrival_order_not_round_robin(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
run = GraphRunStream(None, mux, wire_pump=False)
# interleave() subscribes channels directly and reads _items
# for stamp-ordered iteration. We simulate the pump by wiring
# a custom callback that pushes items in a known order.
push_script = [
("alpha", "a1"),
("alpha", "a2"),
("beta", "b1"),
("alpha", "a3"),
("beta", "b2"),
]
push_iter = iter(push_script)
channels = {"alpha": alpha, "beta": beta}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
names = [name for name, _ in result]
items = [item for _, item in result]
assert items == ["a1", "a2", "b1", "a3", "b2"]
assert names == ["alpha", "alpha", "beta", "alpha", "beta"]
def test_single_projection(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
push_script = [("alpha", "a1"), ("alpha", "a2")]
push_iter = iter(push_script)
def fake_pump() -> bool:
try:
_, item = next(push_iter)
alpha.push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha"))
assert result == [("alpha", "a1"), ("alpha", "a2")]
def test_empty_projection(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
push_script = [("alpha", "a1"), ("alpha", "a2")]
push_iter = iter(push_script)
channels = {"alpha": alpha}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
assert result == [("alpha", "a1"), ("alpha", "a2")]
def test_unknown_projection_raises(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
run = GraphRunStream(None, mux, wire_pump=False)
mux.close()
with pytest.raises((KeyError, AttributeError)):
list(run.interleave("alpha", "does_not_exist"))
def test_all_empty(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
run = GraphRunStream(None, mux, wire_pump=False)
def fake_pump() -> bool:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
assert result == []
def test_error_propagation(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
run = GraphRunStream(None, mux, wire_pump=False)
err = RuntimeError("boom")
push_script = [
("alpha", "a1"),
("beta", "b1"),
]
push_iter = iter(push_script)
channels = {"alpha": alpha, "beta": beta}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
alpha.fail(err)
beta.close()
return False
mux.bind_pump(fake_pump)
collected = []
with pytest.raises(RuntimeError, match="boom"):
for pair in run.interleave("alpha", "beta"):
collected.append(pair)
assert ("alpha", "a1") in collected
assert ("beta", "b1") in collected
# ---------------------------------------------------------------------------
# Integration test: interleave with stream_events(version="v3")
# ---------------------------------------------------------------------------
class TestInterleaveIntegration:
def test_interleave_values_and_messages(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
tagged = list(run.interleave("values", "messages"))
names = [name for name, _ in tagged]
assert set(names).issubset({"values", "messages"})
assert names.count("values") >= 1
def test_interleave_rejects_already_subscribed(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
# Subscribe alpha via iter first
_ = iter(alpha)
mux.close()
with pytest.raises(RuntimeError, match="already has a subscriber"):
list(run.interleave("alpha"))
def test_interleave_releases_projections_on_completion(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
list(run.interleave("values", "messages"))
# Subscriptions should be released after the generator completes,
# so the channels can be re-iterated (they'll be empty / closed).
assert run.extensions["values"]._subscribed is False
assert run.extensions["messages"]._subscribed is False
def test_interleave_releases_projections_on_early_break(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
gen = run.interleave("values", "messages")
next(gen)
gen.close()
assert run.extensions["values"]._subscribed is False
assert run.extensions["messages"]._subscribed is False
def test_interleave_releases_projections_on_validation_failure(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
# Pre-subscribe alpha so that interleave will fail validation when
# it gets to the second name. The first (already-validated) channel
# should still be released.
run = GraphRunStream(None, mux, wire_pump=False)
mux.close()
alpha._subscribed = True
with pytest.raises(RuntimeError, match="already has a subscriber"):
list(run.interleave("values", "alpha"))
assert mux.extensions["values"]._subscribed is False
-6
View File
@@ -1396,7 +1396,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -1459,7 +1458,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -1512,7 +1510,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -6884,7 +6881,6 @@ def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "router_node"),
"langgraph_checkpoint_ns": AnyStr("router_node:"),
"checkpoint_ns": AnyStr("router_node:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -6912,7 +6908,6 @@ def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "model_node"),
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
"checkpoint_ns": AnyStr("weather_graph:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -6949,7 +6944,6 @@ def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "router_node"),
"langgraph_checkpoint_ns": AnyStr("router_node:"),
"checkpoint_ns": AnyStr("router_node:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -1147,7 +1147,6 @@ async def test_prebuilt_tool_chat() -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -1210,7 +1209,6 @@ async def test_prebuilt_tool_chat() -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -1263,7 +1261,6 @@ async def test_prebuilt_tool_chat() -> None:
"langgraph_path": (PULL, "agent"),
"langgraph_checkpoint_ns": AnyStr("agent:"),
"checkpoint_ns": AnyStr("agent:"),
"_type": "generic-fake-chat-model",
"ls_provider": "fakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -3981,7 +3978,6 @@ async def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "router_node"),
"langgraph_checkpoint_ns": AnyStr("router_node:"),
"checkpoint_ns": AnyStr("router_node:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -4009,7 +4005,6 @@ async def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "model_node"),
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
"checkpoint_ns": AnyStr("weather_graph:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -4046,7 +4041,6 @@ async def test_weather_subgraph(
"langgraph_path": ("__pregel_pull", "router_node"),
"langgraph_checkpoint_ns": AnyStr("router_node:"),
"checkpoint_ns": AnyStr("router_node:"),
"_type": "fake-messages-list-chat-model",
"ls_provider": "fakemessageslistchatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
+353 -19
View File
@@ -16,7 +16,7 @@ from typing import Annotated, Any, Literal, get_type_hints
import pytest
from langchain_core.language_models import GenericFakeChatModel
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, RemoveMessage
from langchain_core.runnables import (
RunnableConfig,
RunnableLambda,
@@ -25,6 +25,7 @@ from langchain_core.runnables import (
from langchain_core.runnables.graph import Edge
from langgraph.cache.base import BaseCache
from langgraph.checkpoint.base import (
DELTA_SENTINEL,
BaseCheckpointSaver,
Checkpoint,
CheckpointMetadata,
@@ -41,6 +42,7 @@ from typing_extensions import NotRequired, TypedDict
from langgraph._internal._constants import CONFIG_KEY_NODE_FINISHED, ERROR, PULL
from langgraph.channels.binop import BinaryOperatorAggregate
from langgraph.channels.delta import DeltaChannel
from langgraph.channels.ephemeral_value import EphemeralValue
from langgraph.channels.last_value import LastValue
from langgraph.channels.topic import Topic
@@ -49,7 +51,7 @@ from langgraph.config import get_stream_writer
from langgraph.errors import GraphRecursionError, InvalidUpdateError, ParentCommand
from langgraph.func import entrypoint, task
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import MessagesState, add_messages
from langgraph.graph.message import MessagesState, _messages_delta_reducer, add_messages
from langgraph.pregel import (
NodeBuilder,
Pregel,
@@ -120,6 +122,29 @@ def test_graph_validation() -> None:
graph.invoke({"hello": "there"})
def test_request_drain_allows_inflight_call_scheduling(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
from langgraph.runtime import RunControl
@task
def child(x: int) -> int:
return x + 1
control = RunControl()
@entrypoint(checkpointer=sync_checkpointer)
def graph(x: int) -> int:
control.request_drain()
fut = child(x)
return fut.result()
config = {"configurable": {"thread_id": "drain-call-sync"}}
assert graph.invoke(1, config=config, control=control) == 2
assert control.drain_requested
def test_invalid_checkpointer_type() -> None:
class State(TypedDict):
foo: str
@@ -615,8 +640,11 @@ def test_run_from_checkpoint_id_retains_previous_writes(
)
]
assert len(new_history) == len(history) + 1
for original, new in zip(history, new_history[1:]):
# +2: one fork checkpoint from time travel, one from the new execution
assert len(new_history) == len(history) + 2
# new_history[0] is the new execution result, new_history[1] is the fork
assert new_history[1].metadata["source"] == "fork"
for original, new in zip(history, new_history[2:]):
assert original.values == new.values
assert original.next == new.next
assert original.metadata["step"] == new.metadata["step"]
@@ -624,7 +652,7 @@ def test_run_from_checkpoint_id_retains_previous_writes(
def _get_tasks(hist: list, start: int):
return [h.tasks for h in hist[start:]]
assert _get_tasks(new_history, 1) == _get_tasks(history, 0)
assert _get_tasks(new_history, 2) == _get_tasks(history, 0)
def test_batch_two_processes_in_out() -> None:
@@ -1329,20 +1357,22 @@ def test_imp_nested(
}
thread1 = {"configurable": {"thread_id": "1"}}
assert [*graph.stream([0, 1], thread1, durability=durability)] == [
{"submapper": "0"},
result = [*graph.stream([0, 1], thread1, durability=durability)]
# nested tasks run concurrently so output order is non-deterministic
assert sorted(result[:-1], key=lambda d: str(d)) == [
{"mapper": "00"},
{"submapper": "1"},
{"mapper": "11"},
{
"__interrupt__": (
Interrupt(
value="question",
id=AnyStr(),
),
)
},
{"submapper": "0"},
{"submapper": "1"},
]
assert result[-1] == {
"__interrupt__": (
Interrupt(
value="question",
id=AnyStr(),
),
)
}
assert graph.invoke(Command(resume="answer"), thread1, durability=durability) == [
"00answera",
@@ -6269,7 +6299,7 @@ def test_sync_streaming_with_functional_api() -> None:
@task()
def slow() -> dict:
time.sleep(time_delay) # Simulate a delay of 10 ms
return {"tic": time.time()}
return {"tic": time.monotonic()}
@entrypoint()
def graph(inputs: dict) -> list:
@@ -6282,7 +6312,7 @@ def test_sync_streaming_with_functional_api() -> None:
for chunk in graph.stream({}):
if "slow" not in chunk: # We'll just look at the updates from `slow`
continue
arrival_times.append(time.time())
arrival_times.append(time.monotonic())
assert len(arrival_times) == 2
delta = arrival_times[1] - arrival_times[0]
@@ -6891,7 +6921,6 @@ def test_tags_stream_mode_messages() -> None:
"langgraph_path": ("__pregel_pull", "call_model"),
"langgraph_checkpoint_ns": AnyStr("call_model:"),
"checkpoint_ns": AnyStr("call_model:"),
"_type": "generic-fake-chat-model",
"ls_provider": "genericfakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -6901,6 +6930,60 @@ def test_tags_stream_mode_messages() -> None:
]
def test_configurable_propagates_to_stream_metadata() -> None:
"""Regression: thread_id, run_id, assistant_id, graph_id,
and langgraph_auth_user_id from configurable must appear
in stream_mode='messages' metadata."""
def my_node(state):
return {"messages": HumanMessage(content="hello")}
graph = (
StateGraph(MessagesState)
.add_node("my_node", my_node)
.add_edge(START, "my_node")
.compile()
)
config = {
"configurable": {
"thread_id": "th-123",
"checkpoint_id": "ckpt-1",
"checkpoint_ns": "ns-1",
"task_id": "task-1",
"run_id": "run-456",
"assistant_id": "asst-789",
"graph_id": "graph-0",
"model": "gpt-4o",
"user_id": "uid-1",
"cron_id": "cron-1",
"langgraph_auth_user_id": "user-1",
# these should NOT be propagated into metadata
"some_api_key": "secret",
"custom_setting": {"nested": True},
},
}
results = list(graph.stream({"messages": []}, config, stream_mode="messages"))
assert len(results) == 1
_, metadata = results[0]
# propagated keys
assert metadata["thread_id"] == "th-123"
assert metadata["checkpoint_id"] == "ckpt-1"
assert metadata["checkpoint_ns"] == "ns-1"
assert metadata["task_id"] == "task-1"
assert metadata["run_id"] == "run-456"
assert metadata["assistant_id"] == "asst-789"
assert metadata["graph_id"] == "graph-0"
# These are only present in trace metadata by default as of langgraph 1.2
# assert metadata["model"] == "gpt-4o"
# assert metadata["user_id"] == "uid-1"
# assert metadata["cron_id"] == "cron-1"
# assert metadata["langgraph_auth_user_id"] == "user-1"
# non-allowlisted keys must not appear
assert "some_api_key" not in metadata
assert "custom_setting" not in metadata
def test_stream_mode_messages_command() -> None:
from langchain_core.messages import HumanMessage
@@ -9342,3 +9425,254 @@ def test_fork_does_not_apply_pending_writes(
# Should be: 1 (input) + 20 (forked node_a) + 100 (node_b) = 121
assert result == {"value": 121}
async def test_delta_channel_end_to_end_inmemory() -> None:
"""Full graph run: DeltaChannel accumulates correctly across multiple turns."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
n = len(state["messages"])
return {"messages": [AIMessage(content=f"reply-{n}", id=f"ai-{n}")]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "diff-test-1"}}
# Turn 1
graph.invoke({"messages": [HumanMessage(content="hello", id="h1")]}, config)
# Turn 2
graph.invoke({"messages": [HumanMessage(content="world", id="h2")]}, config)
# Turn 3
graph.invoke({"messages": [HumanMessage(content="bye", id="h3")]}, config)
state = graph.get_state(config)
msgs = state.values["messages"]
# 3 human + 3 AI = 6 total
assert len(msgs) == 6, f"expected 6 messages, got {len(msgs)}: {msgs}"
assert msgs[0].content == "hello"
assert msgs[2].content == "world"
assert msgs[4].content == "bye"
assert msgs[1].content == "reply-1"
assert msgs[3].content == "reply-3"
assert msgs[5].content == "reply-5"
async def test_delta_channel_time_travel() -> None:
"""Time-travel back to turn-1 checkpoint and resume; continuation must not include turn-2 deltas."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
counter = {"n": 0}
def respond(state: State) -> dict:
counter["n"] += 1
return {
"messages": [
AIMessage(content=f"ai-{counter['n']}", id=f"ai-{counter['n']}")
]
}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
saver = InMemorySaver()
graph = builder.compile(checkpointer=saver)
config = {"configurable": {"thread_id": "diff-time-travel"}}
# Run 2 turns: h1→ai-1, h2→ai-2
graph.invoke({"messages": [HumanMessage(content="h1", id="h1")]}, config)
graph.invoke({"messages": [HumanMessage(content="h2", id="h2")]}, config)
# Find the checkpoint after turn 1 (2 messages: h1 + ai-1)
history = list(graph.get_state_history(config))
after_turn1 = next(h for h in history if len(h.values.get("messages", [])) == 2)
assert len(after_turn1.values["messages"]) == 2
assert after_turn1.values["messages"][0].content == "h1"
assert after_turn1.values["messages"][1].content == "ai-1"
# Resume from turn-1 checkpoint: inject h3, expect 3 messages total (h1, ai-1, ai-N)
# NOT 5 messages (turn-2 deltas must not bleed into the resumed run)
result = graph.invoke(
{"messages": [HumanMessage(content="h3", id="h3")]},
after_turn1.config,
)
msgs = result["messages"]
# Should be: h1, ai-1, h3, ai-N — 4 messages total
assert len(msgs) == 4, (
f"expected 4 messages after time-travel resume, got {len(msgs)}: {msgs}"
)
assert msgs[0].content == "h1"
assert msgs[1].content == "ai-1"
assert msgs[2].content == "h3"
async def test_delta_channel_remove_message_end_to_end() -> None:
"""RemoveMessage inside a DeltaChannel graph must persist and reload correctly."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
return {"messages": [AIMessage(content="reply", id="ai-1")]}
def delete_first(state: State) -> dict:
# removes the first message
return {"messages": [RemoveMessage(id=state["messages"][0].id)]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_node("delete_first", delete_first)
builder.add_edge(START, "respond")
builder.add_edge("respond", "delete_first")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "diff-remove-test"}}
graph.invoke({"messages": [HumanMessage(content="hello", id="h1")]}, config)
state = graph.get_state(config)
msgs = state.values["messages"]
# h1 was removed, only ai-1 should remain
assert len(msgs) == 1, f"expected 1 message, got {len(msgs)}: {msgs}"
assert msgs[0].id == "ai-1"
# A subsequent turn must reconstruct from the checkpoint correctly
graph.invoke({"messages": [HumanMessage(content="again", id="h2")]}, config)
state = graph.get_state(config)
msgs = state.values["messages"]
# ai-1 + h2 + ai-1(second reply, same id overwrites) + h2 removed
# more simply: after second run we expect ai-1 updated + h2 remaining minus deleted h2
# just assert h1 is still gone
assert all(m.id != "h1" for m in msgs), (
"h1 should still be absent after second turn"
)
async def test_delta_channel_update_by_id_end_to_end() -> None:
"""Updating a message by ID via DeltaChannel must persist and reload correctly."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def update_msg(state: State) -> dict:
# re-send h1 with updated content
return {"messages": [HumanMessage(content="updated", id="h1")]}
builder = StateGraph(State)
builder.add_node("update_msg", update_msg)
builder.add_edge(START, "update_msg")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "diff-update-id-test"}}
graph.invoke({"messages": [HumanMessage(content="original", id="h1")]}, config)
state = graph.get_state(config)
msgs = state.values["messages"]
assert len(msgs) == 1, f"expected 1 message, got {len(msgs)}: {msgs}"
assert msgs[0].content == "updated"
assert msgs[0].id == "h1"
# Second turn: verify the updated state is the base for further accumulation
graph.invoke({"messages": [HumanMessage(content="new", id="h2")]}, config)
state = graph.get_state(config)
msgs = state.values["messages"]
ids = [m.id for m in msgs]
assert "h1" in ids # h1 persists (updated, not duplicated)
assert "h2" in ids
assert ids.count("h1") == 1, "h1 must not be duplicated"
async def test_delta_channel_durability_exit_stores_snapshot() -> None:
"""DeltaChannel must reload from a durability='exit' checkpoint."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
return {"messages": [AIMessage(content="reply", id="ai1")]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "delta-exit-test"}}
result = graph.invoke(
{"messages": [HumanMessage(content="hello", id="h1")]},
config,
durability="exit",
)
assert [m.content for m in result["messages"]] == ["hello", "reply"]
state = graph.get_state(config)
assert [m.content for m in state.values["messages"]] == ["hello", "reply"]
async def test_delta_channel_async_write_ordering() -> None:
"""In async mode, DeltaChannel write futures are awaited before the checkpoint
is committed, so aput_writes always precedes aput for sentinel checkpoints."""
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
def respond(state: State) -> dict:
i = len(state["messages"])
return {"messages": [AIMessage(content=f"r{i}", id=f"ai{i}")]}
order: list[str] = []
original_aput_writes = InMemorySaver.aput_writes
original_aput = InMemorySaver.aput
async def tracked_aput_writes(self, config, writes, task_id, task_path=""):
result = await original_aput_writes(self, config, writes, task_id, task_path)
order.append("aput_writes")
return result
async def tracked_aput(self, config, checkpoint, metadata, new_versions):
has_sentinel = any(
v is DELTA_SENTINEL for v in checkpoint.get("channel_values", {}).values()
)
order.append("aput_sentinel" if has_sentinel else "aput_other")
return await original_aput(self, config, checkpoint, metadata, new_versions)
InMemorySaver.aput_writes = tracked_aput_writes
InMemorySaver.aput = tracked_aput
try:
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "async-ordering-test"}}
for i in range(3):
await graph.ainvoke(
{"messages": [HumanMessage(content=f"h{i}", id=f"h{i}")]}, config
)
# Every aput_sentinel must be preceded by at least one aput_writes
for i, event in enumerate(order):
if event == "aput_sentinel":
preceding = order[:i]
assert "aput_writes" in preceding, (
f"aput_sentinel at {i} had no preceding aput_writes: {order}"
)
last_write_idx = max(
j for j, e in enumerate(order[:i]) if e == "aput_writes"
)
assert last_write_idx < i, (
f"aput_writes at {last_write_idx} should precede aput_sentinel at {i}: {order}"
)
finally:
InMemorySaver.aput_writes = original_aput_writes
InMemorySaver.aput = original_aput
state = await graph.aget_state(config)
assert len(state.values["messages"]) == 6 # 3 human + 3 AI
+247 -4
View File
@@ -16,10 +16,12 @@ from typing import (
Literal,
Optional,
)
from unittest.mock import patch
from uuid import UUID
import pytest
from langchain_core.language_models import GenericFakeChatModel
from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableConfig, RunnableLambda, RunnablePassthrough
from langchain_core.utils.aiter import aclosing
from langgraph.cache.base import BaseCache
@@ -47,6 +49,7 @@ from langgraph.channels.topic import Topic
from langgraph.errors import (
GraphRecursionError,
InvalidUpdateError,
NodeError,
ParentCommand,
)
from langgraph.func import entrypoint, task
@@ -214,6 +217,30 @@ async def test_checkpoint_errors() -> None:
pass
@NEEDS_CONTEXTVARS
async def test_request_drain_allows_inflight_acall_scheduling(
async_checkpointer: BaseCheckpointSaver,
) -> None:
from langgraph.runtime import RunControl
@task
async def child(x: int) -> int:
return x + 1
control = RunControl()
@entrypoint(checkpointer=async_checkpointer)
async def graph(x: int) -> int:
control.request_drain()
fut = child(x)
return await fut
config = {"configurable": {"thread_id": "drain-call-async"}}
assert await graph.ainvoke(1, config=config, control=control) == 2
assert control.drain_requested
async def test_py_async_with_cancel_behavior() -> None:
"""This test confirms that in all versions of Python we support, __aexit__
is not cancelled when the coroutine containing the async with block is cancelled."""
@@ -2085,8 +2112,11 @@ async def test_run_from_checkpoint_id_retains_previous_writes(
)
]
assert len(new_history) == len(history) + 1
for original, new in zip(history, new_history[1:]):
# +2: one fork checkpoint from time travel, one from the new execution
assert len(new_history) == len(history) + 2
# new_history[0] is the new execution result, new_history[1] is the fork
assert new_history[1].metadata["source"] == "fork"
for original, new in zip(history, new_history[2:]):
assert original.values == new.values
assert original.next == new.next
assert original.metadata["step"] == new.metadata["step"]
@@ -2094,7 +2124,7 @@ async def test_run_from_checkpoint_id_retains_previous_writes(
def _get_tasks(hist: list, start: int):
return [h.tasks for h in hist[start:]]
assert _get_tasks(new_history, 1) == _get_tasks(history, 0)
assert _get_tasks(new_history, 2) == _get_tasks(history, 0)
async def test_cond_edge_after_send() -> None:
@@ -6097,6 +6127,36 @@ async def test_parent_command(
)
async def test_delta_channel_durability_exit_stores_snapshot_async() -> None:
"""DeltaChannel must reload from an async durability='exit' checkpoint."""
from langchain_core.messages import AIMessage
from langgraph.channels.delta import DeltaChannel
from langgraph.graph.message import _messages_delta_reducer
class State(TypedDict):
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
async def respond(state: State) -> dict:
return {"messages": [AIMessage(content="reply", id="ai1")]}
builder = StateGraph(State)
builder.add_node("respond", respond)
builder.add_edge(START, "respond")
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "delta-exit-async-test"}}
result = await graph.ainvoke(
{"messages": [HumanMessage(content="hello", id="h1")]},
config,
durability="exit",
)
assert [m.content for m in result["messages"]] == ["hello", "reply"]
state = await graph.aget_state(config)
assert [m.content for m in state.values["messages"]] == ["hello", "reply"]
@NEEDS_CONTEXTVARS
async def test_interrupt_subgraph(async_checkpointer: BaseCheckpointSaver) -> None:
class State(TypedDict):
@@ -7541,7 +7601,6 @@ async def test_tags_stream_mode_messages() -> None:
"langgraph_path": ("__pregel_pull", "call_model"),
"langgraph_checkpoint_ns": AnyStr("call_model:"),
"checkpoint_ns": AnyStr("call_model:"),
"_type": "generic-fake-chat-model",
"ls_provider": "genericfakechatmodel",
"ls_model_type": "chat",
"ls_integration": "langchain_chat_model",
@@ -7551,6 +7610,67 @@ async def test_tags_stream_mode_messages() -> None:
]
async def test_configurable_propagates_to_stream_metadata() -> None:
"""Regression: thread_id, run_id, assistant_id, graph_id,
and langgraph_auth_user_id from configurable must appear
in stream_mode='messages' metadata."""
def my_node(state):
return {"messages": HumanMessage(content="hello")}
graph = (
StateGraph(MessagesState)
.add_node("my_node", my_node)
.add_edge(START, "my_node")
.compile()
)
config = {
"configurable": {
"thread_id": "th-123",
"checkpoint_id": "ckpt-1",
"checkpoint_ns": "ns-1",
"task_id": "task-1",
"run_id": "run-456",
"assistant_id": "asst-789",
"graph_id": "graph-0",
"model": "gpt-4o",
"user_id": "uid-1",
"cron_id": "cron-1",
"langgraph_auth_user_id": "user-1",
# these should NOT be propagated into metadata
"some_api_key": "secret",
"custom_setting": {"nested": True},
},
}
results = [
chunk
async for chunk in graph.astream(
{"messages": []}, config, stream_mode="messages"
)
]
assert len(results) == 1
_, metadata = results[0]
# propagated keys
assert metadata["thread_id"] == "th-123"
assert metadata["checkpoint_id"] == "ckpt-1"
assert metadata["checkpoint_ns"] == "ns-1"
assert metadata["task_id"] == "task-1"
assert metadata["run_id"] == "run-456"
assert metadata["assistant_id"] == "asst-789"
assert metadata["graph_id"] == "graph-0"
# These will only be traced as of langgraph 1.2 and not present by default in
# metadata
# assert metadata["model"] == "gpt-4o"
# assert metadata["user_id"] == "uid-1"
# assert metadata["cron_id"] == "cron-1"
# assert metadata["langgraph_auth_user_id"] == "user-1"
# non-allowlisted keys must not appear
assert "some_api_key" not in metadata
assert "custom_setting" not in metadata
async def test_stream_mode_messages_command() -> None:
from langchain_core.messages import HumanMessage
@@ -9645,3 +9765,126 @@ async def test_fork_does_not_apply_pending_writes(
# 1 (input) + 20 (forked node_a) + 100 (node_b) = 121
assert result == {"value": 121}
async def test_graph_error_handler_async_runtime_info() -> None:
class State(TypedDict):
foo: str
attempts = 0
captured: dict[str, object] = {}
async def always_failing_node(state: State) -> State:
nonlocal attempts
attempts += 1
raise ValueError("Always fails async")
async def err_handler_node(state: State, error: NodeError) -> State:
captured["from_node_name"] = error.node
captured["from_node_error"] = error.error
return {"foo": "handled_async"}
graph = (
StateGraph(State)
.add_node(
"always_failing",
always_failing_node,
retry_policy=RetryPolicy(
max_attempts=2,
initial_interval=0.01,
jitter=False,
retry_on=ValueError,
),
error_handler=err_handler_node,
)
.add_edge(START, "always_failing")
.compile()
)
with patch("asyncio.sleep"):
result = await graph.ainvoke({"foo": ""})
assert attempts == 2
assert result["foo"] == "handled_async"
assert captured["from_node_name"] == "always_failing"
assert isinstance(captured["from_node_error"], BaseException)
@NEEDS_CONTEXTVARS
async def test_graph_error_handler_does_not_swallow_interrupt_concurrent() -> None:
"""When a graph error handler is configured and a node calls interrupt()
concurrently with other nodes, the interrupt must still be raised not
silently swallowed."""
class State(TypedDict):
foo: str
async def node_a(state: State) -> State:
val = interrupt("need human input")
return {"foo": f"a_{val}"}
async def node_b(state: State) -> State:
return {}
async def err_handler(state: State) -> State:
return {"foo": "handled"}
checkpointer = InMemorySaver()
graph = (
StateGraph(State)
.add_node("node_a", node_a, error_handler=err_handler)
.add_node("node_b", node_b)
.add_edge(START, "node_a")
.add_edge(START, "node_b")
.compile(checkpointer=checkpointer)
)
config = {"configurable": {"thread_id": "test-interrupt-concurrent-async"}}
await graph.ainvoke({"foo": ""}, config)
state = await graph.aget_state(config)
assert len(state.tasks) > 0
interrupts = [t for t in state.tasks if hasattr(t, "interrupts") and t.interrupts]
assert len(interrupts) > 0, (
"GraphInterrupt was swallowed — interrupt() in node_a "
"should have paused execution"
)
async def test_node_error_handler_handles_subgraph_internal_failure_async() -> None:
class SubState(TypedDict):
foo: str
class ParentState(TypedDict):
foo: str
captured: dict[str, object] = {}
async def sub_fail_node(state: SubState) -> SubState:
raise ValueError("async subgraph boom")
async def parent_handler(state: ParentState, error: NodeError) -> ParentState:
captured["from_node_name"] = error.node
captured["from_node_error"] = error.error
return {"foo": "handled_async_subgraph"}
subgraph = (
StateGraph(SubState)
.add_node("sub_fail_node", sub_fail_node)
.add_edge(START, "sub_fail_node")
.compile()
)
parent_graph = (
StateGraph(ParentState)
.add_node("subgraph_node", subgraph, error_handler=parent_handler)
.add_edge(START, "subgraph_node")
.compile()
)
result = await parent_graph.ainvoke({"foo": ""})
assert result["foo"] == "handled_async_subgraph"
assert captured["from_node_name"] == "subgraph_node"
assert isinstance(captured["from_node_error"], BaseException)

Some files were not shown because too many files have changed in this diff Show More