diff --git a/Pipfile.lock b/Pipfile.lock
index c85970ed..3fa163a5 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -160,45 +160,45 @@
},
"alembic": {
"hashes": [
- "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc",
- "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e"
+ "sha256:297950a8a91f6770eb82bfbce9bea55c728b90a5386c6e81430191a319d138b0",
+ "sha256:32d553dcd577e6fe5c3c63e91468526d35e4dcecafe865d7db4e9b328fa93cb2"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==1.18.5"
+ "version": "==1.19.2"
},
"annotated-doc": {
"hashes": [
- "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320",
- "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"
+ "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101",
+ "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb"
],
- "markers": "python_version >= '3.8'",
- "version": "==0.0.4"
+ "markers": "python_version >= '3.9'",
+ "version": "==0.0.5"
},
"annotated-types": {
"hashes": [
- "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53",
- "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"
+ "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7",
+ "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0"
],
"index": "pypi",
- "markers": "python_version >= '3.8'",
- "version": "==0.7.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==0.8.0"
},
"anthropic": {
"hashes": [
- "sha256:524d835869b8e374510b3bcc552e28dbdafc035a61d63fc0088c4035286ef8fe",
- "sha256:acb3c43b7e7592cf635fa21930e67a99d32641d68ccd53f625b52f71d7870d55"
+ "sha256:590e85bff75b713a123b03f586d68f02266b5fdc49f70dd75f721ced93a4716c",
+ "sha256:f0d017e901e48b343520b5d458f8240c283c8d850bf6d119834c622207e0a74c"
],
- "markers": "python_version >= '3.9'",
- "version": "==0.118.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==1.4.0"
},
"anyio": {
"hashes": [
- "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494",
- "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f"
+ "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101",
+ "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94"
],
"markers": "python_version >= '3.10'",
- "version": "==4.14.2"
+ "version": "==4.15.1"
},
"asgiref": {
"hashes": [
@@ -226,20 +226,20 @@
},
"boto3": {
"hashes": [
- "sha256:3fe21f37f5b34e00fc360190f363229cef425f3c0e80a6f2b6a52f6501c3ec90",
- "sha256:8353beda3ce4037758b24ab6bce7162e6cc084fd14c88637956d208d76cee244"
+ "sha256:c28abbe472e9b7cad08807356311aeec51bde5218c18489da827045d2267bfd9",
+ "sha256:fe4190afe63eb562b6ba6a3911cf4427473b35fa047adde093bf696d3ae09fc0"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==1.43.54"
+ "version": "==1.43.89"
},
"botocore": {
"hashes": [
- "sha256:22b447e6c5e295d610a00df262efdffb062805d293c00e94f52a4cd3fe9d391d",
- "sha256:5b58969503eda747e0b69c33735edfc02dab3d31fe3bba87dd83ea787152ffa4"
+ "sha256:d7211220c815427fe71225acc6909e4ab5dfab3b03770e72fd16cf9eb86b3d1a",
+ "sha256:f0574942970742657b0e0716cf08c2dfe6bef8e6de5fbb7081c3424e262b4cca"
],
"markers": "python_version >= '3.10'",
- "version": "==1.43.54"
+ "version": "==1.43.89"
},
"certifi": {
"hashes": [
@@ -251,109 +251,109 @@
},
"cffi": {
"hashes": [
- "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc",
- "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd",
- "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d",
- "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5",
- "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f",
- "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6",
- "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c",
- "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda",
- "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd",
- "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a",
- "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd",
- "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd",
- "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3",
- "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb",
- "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66",
- "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d",
- "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f",
- "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6",
- "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0",
- "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c",
- "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93",
- "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d",
- "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d",
- "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8",
- "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b",
- "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001",
- "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d",
- "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43",
- "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b",
- "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0",
- "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0",
- "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458",
- "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8",
- "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d",
- "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94",
- "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022",
- "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db",
- "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479",
- "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376",
- "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d",
- "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6",
- "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3",
- "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea",
- "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd",
- "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02",
- "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde",
- "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224",
- "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76",
- "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804",
- "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1",
- "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913",
- "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714",
- "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc",
- "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2",
- "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e",
- "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda",
- "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512",
- "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28",
- "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699",
- "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3",
- "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c",
- "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe",
- "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a",
- "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f",
- "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c",
- "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2",
- "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f",
- "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b",
- "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565",
- "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056",
- "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629",
- "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7",
- "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0",
- "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9",
- "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853",
- "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13",
- "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a",
- "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4",
- "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce",
- "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac",
- "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c",
- "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46",
- "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384",
- "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b",
- "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210",
- "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc",
- "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a",
- "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5",
- "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7",
- "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2",
- "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326",
- "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f",
- "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca",
- "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98",
- "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9",
- "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5",
- "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7",
- "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc",
- "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da",
- "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f"
+ "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e",
+ "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66",
+ "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2",
+ "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0",
+ "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6",
+ "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971",
+ "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c",
+ "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d",
+ "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9",
+ "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517",
+ "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735",
+ "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80",
+ "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f",
+ "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1",
+ "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29",
+ "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8",
+ "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c",
+ "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e",
+ "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48",
+ "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813",
+ "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac",
+ "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632",
+ "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6",
+ "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1",
+ "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659",
+ "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688",
+ "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004",
+ "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0",
+ "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062",
+ "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779",
+ "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94",
+ "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50",
+ "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab",
+ "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac",
+ "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6",
+ "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676",
+ "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1",
+ "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9",
+ "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf",
+ "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13",
+ "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e",
+ "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e",
+ "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973",
+ "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527",
+ "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72",
+ "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890",
+ "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c",
+ "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990",
+ "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd",
+ "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9",
+ "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94",
+ "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3",
+ "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80",
+ "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41",
+ "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5",
+ "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c",
+ "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a",
+ "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4",
+ "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e",
+ "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6",
+ "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98",
+ "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b",
+ "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1",
+ "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03",
+ "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af",
+ "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231",
+ "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2",
+ "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3",
+ "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836",
+ "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5",
+ "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399",
+ "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96",
+ "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e",
+ "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be",
+ "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf",
+ "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc",
+ "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455",
+ "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0",
+ "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12",
+ "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b",
+ "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7",
+ "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692",
+ "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54",
+ "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3",
+ "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b",
+ "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be",
+ "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d",
+ "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358",
+ "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a",
+ "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7",
+ "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc",
+ "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960",
+ "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125",
+ "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb",
+ "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a",
+ "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa",
+ "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf",
+ "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3",
+ "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4",
+ "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264"
],
"markers": "python_version >= '3.10'",
- "version": "==2.1.0"
+ "version": "==2.1.1"
},
"charset-normalizer": {
"hashes": [
@@ -535,63 +535,63 @@
},
"click": {
"hashes": [
- "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6",
- "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"
+ "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360",
+ "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34"
],
"markers": "python_version >= '3.10'",
- "version": "==8.4.2"
+ "version": "==8.5.0"
},
"cryptography": {
"hashes": [
- "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001",
- "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122",
- "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6",
- "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c",
- "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325",
- "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69",
- "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d",
- "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36",
- "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc",
- "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6",
- "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b",
- "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27",
- "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61",
- "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18",
- "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db",
- "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b",
- "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb",
- "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2",
- "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459",
- "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e",
- "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21",
- "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8",
- "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7",
- "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa",
- "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9",
- "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db",
- "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64",
- "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505",
- "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5",
- "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615",
- "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f",
- "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866",
- "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6",
- "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561",
- "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838",
- "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9",
- "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7",
- "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68",
- "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8",
- "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3",
- "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e",
- "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a",
- "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d",
- "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4",
- "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493",
- "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b"
+ "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71",
+ "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23",
+ "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6",
+ "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e",
+ "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361",
+ "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054",
+ "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f",
+ "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6",
+ "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49",
+ "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5",
+ "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149",
+ "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88",
+ "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad",
+ "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a",
+ "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f",
+ "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2",
+ "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20",
+ "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45",
+ "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f",
+ "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b",
+ "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527",
+ "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3",
+ "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6",
+ "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367",
+ "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0",
+ "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94",
+ "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239",
+ "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b",
+ "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a",
+ "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9",
+ "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5",
+ "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc",
+ "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648",
+ "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986",
+ "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959",
+ "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0",
+ "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17",
+ "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e",
+ "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733",
+ "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f",
+ "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8",
+ "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf",
+ "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671",
+ "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80",
+ "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558",
+ "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef"
],
"markers": "python_version >= '3.9' and python_full_version not in '3.9.0, 3.9.1'",
- "version": "==49.0.0"
+ "version": "==50.0.1"
},
"distro": {
"hashes": [
@@ -636,12 +636,12 @@
},
"fastapi": {
"hashes": [
- "sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e",
- "sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c"
+ "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3",
+ "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==0.139.2"
+ "version": "==0.141.1"
},
"filetype": {
"hashes": [
@@ -791,20 +791,20 @@
"requests"
],
"hashes": [
- "sha256:c8270ea95b2697b74e3d8438ae9c5b898e38b623b915c7b5c5635921e7de68a6",
- "sha256:e28f103ca8091fb7012b99c44243d7366c29863713b8e34a220c3322b7a07051"
+ "sha256:ab439dee60a6856412bc058f13a68eb6a59c5b81526bb89cfdcf89ce9c0a48c9",
+ "sha256:eb47b230fc6707eed4aee1c9cef55ec05bc1785eecba74ff8b572d531e921b1e"
],
"markers": "python_version >= '3.10'",
- "version": "==2.56.2"
+ "version": "==2.57.1"
},
"google-genai": {
"hashes": [
- "sha256:a9d1f4f362d76280f1be1340fcb3c86e63dbca128f6a4ae09d86ab47ff7148e8",
- "sha256:ae7172cdd35695189b516b33a878e4132e5daa2dbc03a5b44cddfa8a82fad664"
+ "sha256:9fa3b5d9ddb635005d8ab2d6206fb2b3d7204b66965bbce7de13ecd1a866ebcd",
+ "sha256:c514001c45470cc0a942440ae1b8215445d12bfb6c373aac94637127e1f74ec6"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==2.14.0"
+ "version": "==2.22.0"
},
"googleapis-common-protos": {
"hashes": [
@@ -873,12 +873,12 @@
},
"gunicorn": {
"hashes": [
- "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc",
- "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf"
+ "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447",
+ "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==26.0.0"
+ "version": "==26.2.0"
},
"h11": {
"hashes": [
@@ -905,6 +905,14 @@
"markers": "python_version >= '3.8'",
"version": "==1.0.9"
},
+ "httpcore2": {
+ "hashes": [
+ "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb",
+ "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648"
+ ],
+ "markers": "python_version >= '3.10'",
+ "version": "==2.12.0"
+ },
"httpx": {
"hashes": [
"sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc",
@@ -922,6 +930,14 @@
"markers": "python_version >= '3.9'",
"version": "==0.4.3"
},
+ "httpx2": {
+ "hashes": [
+ "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf",
+ "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36"
+ ],
+ "markers": "python_version >= '3.10'",
+ "version": "==2.12.0"
+ },
"idna": {
"hashes": [
"sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15",
@@ -1076,21 +1092,21 @@
},
"langchain": {
"hashes": [
- "sha256:1b6696c72ba3bbbce54d745e0180742c9f6ece8bbc59ed5a46c3e20b9a435929",
- "sha256:4d10dbe91005952cddd56d0dc77aa108964da6bae90ab20063653957e901f782"
+ "sha256:08da122a439f738f4c09a5158cfa3d2c1d8bea2d0bde7fbbf4aeb4d7f7fd9d80",
+ "sha256:af1dc0161d30944a52ec7844d9bf890d0497b12ec0703069f3503b4003cbbac3"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==1.3.14"
+ "version": "==1.4.0"
},
"langchain-anthropic": {
"hashes": [
- "sha256:b341c0fa197e7d55555a228377e66214af8cb77631ca633f8bd58655d10103ac",
- "sha256:c36f195fd73455d820f4e7cf7220d652858d707b67bce70f3fe0f57227ec6c81"
+ "sha256:ac087159e1356ae933d5443ded2bca72912a2ed34ea171520fcfdaddfb838687",
+ "sha256:c461166def7b7d9d05de518d80afd041a6fcd0d49b07d3216d2a6b9a5069cb37"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==1.5.0"
+ "version": "==1.7.1"
},
"langchain-classic": {
"hashes": [
@@ -1111,47 +1127,47 @@
},
"langchain-core": {
"hashes": [
- "sha256:e1fa09d55b354192c8f60dade06a55bd6add2318c822a684555b8d4a30a16143",
- "sha256:f122efee35446632b38687119fca33711abbf3b6b555e31156762298fbe78a65"
+ "sha256:1ec6d3a98f7c8cdbb5bb0deff86e7ca37e16bf4f8f49f022d10723656c62352d",
+ "sha256:21e6c7cf097c68b777fd69ea00864f09bd9ca76ac73e3e3fa14e1ee366eb4711"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==1.5.0"
+ "version": "==1.6.2"
},
"langchain-google-genai": {
"hashes": [
- "sha256:0ebec25bfa62e75a65561b92fde5bdf8d87618bdda9371afe57d6b86bbb9cda8",
- "sha256:623cd4329f6a3e4e0397ccacc6fa355728362572688ff9a8e522bf21519e9964"
+ "sha256:7871beec56ac07b719f77c46997845db6ff2267b817bffb0f97877053b0895d7",
+ "sha256:8e23a1307bd2158590bbf9d99f1d658fc84a9d9ddb77fb1b372f8875a2bafbbf"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==4.3.1"
+ "version": "==4.4.0"
},
"langchain-openai": {
"hashes": [
- "sha256:7a777731fe32a913085ec85bacd5650c3f8422048b65346b63a13b36b1b4a12f",
- "sha256:a3acf6be0937f3970fc9e7f0aae22929c6f117e49128bd62f4d45a64b2587d8b"
+ "sha256:648112bbd135aa51d60d4aa2fd4ad353628a8d92a2f8e779244a0d9869a16f18",
+ "sha256:7c545cc1f2bee048a7e219f375b78cd820b32c876ccf825320e25e1e94132577"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==1.4.0"
+ "version": "==1.6.0"
},
"langchain-perplexity": {
"hashes": [
- "sha256:bfde8a61326257a47a35697908ae7f72ab49d6d887894d1da0e08007b6182602",
- "sha256:c39458651dfbb5d9925275ba119050d5660b0946ecfa2aa770eeda298d83ed71"
+ "sha256:3ebe86ccaaef14245c345ffeb775c724907c3235f9f9088cc60169e639de68e8",
+ "sha256:f286ffc666e3da34fb1f7f07d8bcbf7ceb0e94ee6e6f46cbef327ad5fbb0ec57"
],
"index": "pypi",
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==1.4.0"
+ "version": "==1.4.1"
},
"langchain-protocol": {
"hashes": [
- "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a",
- "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6"
+ "sha256:4cdf879a492a35980fd859ae792d3c65458ccaae504e183c9a10d7eac1f0720f",
+ "sha256:79d90a1425122ac87e8052e2ec054fbd09c3edbf341bdfb6397112a495c7bf8c"
],
"markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'",
- "version": "==0.0.18"
+ "version": "==0.0.19"
},
"langchain-text-splitters": {
"hashes": [
@@ -1172,19 +1188,19 @@
},
"langgraph": {
"hashes": [
- "sha256:385f87bc1802c35af7e0aa479278ecba8582d103515eb48256cb2ddcd42d0bd4",
- "sha256:c2d98ad94333937922ba04148641c1da2bfe45b5b8e55d7b6dcb0bb2df809e76"
+ "sha256:8bab70de7b2d00b5300fb289bcf38d8b241400f3184c1e95e8ce706fb0e8686b",
+ "sha256:9ecfe11e50d338b34b15cf4d8a442642de103e8ae6971320efba84e4542eb363"
],
"markers": "python_version >= '3.10'",
- "version": "==1.2.9"
+ "version": "==1.2.11"
},
"langgraph-checkpoint": {
"hashes": [
- "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e",
- "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25"
+ "sha256:0547fd228935a0b758865de3a3d6d7a2537c308895d0f9ab092ce9151b5da942",
+ "sha256:51a593b6bee684b0818e5d6e58e28ab340c6db7794575056ce7bd1b746a84ed7"
],
"markers": "python_version >= '3.10'",
- "version": "==4.1.1"
+ "version": "==4.2.0"
},
"langgraph-prebuilt": {
"hashes": [
@@ -1196,167 +1212,209 @@
},
"langgraph-sdk": {
"hashes": [
- "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd",
- "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738"
+ "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64",
+ "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b"
],
"markers": "python_version >= '3.10'",
- "version": "==0.4.2"
+ "version": "==0.4.4"
},
"langsmith": {
"hashes": [
- "sha256:e0e175e9dd8de6d96dbb55244482e20590a2691ec7c5e087afc1f302e33d98fe",
- "sha256:eb5e9da635f5853fc657cddd1c27f40a8e8b2f00c38051555d608c8e57eb605d"
+ "sha256:ca2b6386cf452cf8edebf27c9c476503c18bad895e7ad566578393fa0ba7fea2",
+ "sha256:ff369ba4390e0969dbb109e838281404ae637e76ef73b347336bae05b4f87331"
],
"markers": "python_version >= '3.10'",
- "version": "==0.10.10"
+ "version": "==0.12.2"
},
"lxml": {
"hashes": [
- "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2",
- "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9",
- "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60",
- "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c",
- "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7",
- "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a",
- "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83",
- "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072",
- "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8",
- "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462",
- "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0",
- "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085",
- "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f",
- "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30",
- "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1",
- "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77",
- "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740",
- "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b",
- "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c",
- "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621",
- "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e",
- "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8",
- "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca",
- "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730",
- "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245",
- "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1",
- "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004",
- "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d",
- "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52",
- "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5",
- "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf",
- "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc",
- "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533",
- "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834",
- "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947",
- "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a",
- "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2",
- "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438",
- "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc",
- "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea",
- "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6",
- "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e",
- "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c",
- "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383",
- "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955",
- "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe",
- "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074",
- "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c",
- "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a",
- "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb",
- "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186",
- "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d",
- "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1",
- "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f",
- "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6",
- "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736",
- "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6",
- "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2",
- "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b",
- "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7",
- "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14",
- "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009",
- "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca",
- "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4",
- "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635",
- "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee",
- "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e",
- "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9",
- "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603",
- "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08",
- "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080",
- "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525",
- "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5",
- "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f",
- "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067",
- "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e",
- "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3",
- "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f",
- "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485",
- "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13",
- "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383",
- "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315",
- "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e",
- "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c",
- "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd",
- "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099",
- "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660",
- "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510",
- "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a",
- "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b",
- "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5",
- "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf",
- "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28",
- "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00",
- "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef",
- "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1",
- "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955",
- "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590",
- "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137",
- "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf",
- "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40",
- "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88",
- "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e",
- "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840",
- "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2",
- "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca",
- "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3",
- "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465",
- "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc",
- "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d",
- "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d",
- "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa",
- "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a",
- "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a",
- "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206",
- "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e",
- "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785",
- "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8",
- "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a",
- "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b",
- "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2",
- "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6",
- "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6",
- "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354",
- "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818",
- "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84",
- "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909",
- "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038",
- "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d",
- "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2",
- "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf",
- "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc",
- "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d",
- "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e"
+ "sha256:032a0a97eed428bd143c75a11118238546424ceb2fa311cca5f073aa44658dc4",
+ "sha256:05f5bce9af14fd1506997594bd81cee6d9c6b58ea80a39c058327aa6371ed9e9",
+ "sha256:0794e04ba343852c6d78e996c58ef4b8e579b4ecc72f8df0d4058bf843b4c96e",
+ "sha256:0ab2467e405e748d93495fb5568e74044802b8d3ff2b2a1607c3f78c6e982de5",
+ "sha256:0bf5a3e397df2ec4258eb5eea4c1ac6cf013ca1abd04a176903bff20a70021fe",
+ "sha256:0c0710ac085a157b593c38fbcacd950f15c4afa8e2057527185875ab302752bc",
+ "sha256:0dee106e9aa97fb00541b1ed7827070564d0549c3d3fba8920e6b20fd980f748",
+ "sha256:0f17d83c48ee9dfd96abae3ac3e2108c76d2fc86ce96355e37b8da9f7f4ecc08",
+ "sha256:0feebef8d0521188d0157f758356072e840173aa61ca45b8b3f87959ac283dd5",
+ "sha256:13a620a3fcc20023f9e6ed5c383e00e826f1c2d5db554df2f67240760f9118e8",
+ "sha256:13d22c0d57355366b393936acf6b98a5e0edeadddd3fccbc6a846c50a76b8741",
+ "sha256:160fcf381f76c3aeac28a756bec44f48942a8f7245a87aa28e3a523b4d90cd87",
+ "sha256:16148acd77ed1d8836a56db883af2f5eed720f9723088110b16a0d08582130a6",
+ "sha256:170773d8a3cdc76259065523ddd978c44f9806e28605f08812e8f86783e44ac6",
+ "sha256:18293f8a8d8b6a8e71ef37706b659e3846a4261232158167b1ddf35f6994f633",
+ "sha256:18a4db52b5a7b53a3540b0b0f4123319334621ee8083d496de314d0bf06ff59a",
+ "sha256:1a635e837b50a1819bebfedaac5916498ea024120969da8790500148fb0a894d",
+ "sha256:1aeca87830c4fe649dcf93fe2b059525b71c72587f21be4ae4af7103082a79fa",
+ "sha256:1b7c37339d7e75cab9a123a04248e243cefefb302ad6db566ea0c77cbcde421e",
+ "sha256:1beb0f9909b26cee938df9ba56b15252a84429b1fc30ce6fca161390b9789a70",
+ "sha256:20384c2bbcbf87180c8c61eb60869699c1ec0cd09b62cfd13804022d860b0867",
+ "sha256:20428910dae17a1a93152a3ff2c0441d2f4932992c0797d65651dd0561f1792f",
+ "sha256:207dfc3d47cf0e575e643bbc140dacc8863b39abaa1e5307cd64c7f2365b8a12",
+ "sha256:209c3ccbfe35a04ac6d24f0611f9d1cbf8025d49991b14acd935236234d6c156",
+ "sha256:2123e5aa075ac20d23c7af489255efd129cbfe190dbe88fd42598cc9df3199b6",
+ "sha256:21402998e4b78e7cce237d2788841aaa21ac9a4d1574d04dc2d12ee41ae807b5",
+ "sha256:2221e88679d1351e9a40aaee54bc65679b9795bbd0160bc3d5e36b163344eb75",
+ "sha256:22eec57e26c418cde02c051ce9914a365e52a7f135a565c6f0480242aeebab48",
+ "sha256:23c366231259cd75ad06495174701afb3fcb36a92917fa47de2d1f1bd9d95739",
+ "sha256:25f4118c438f96bb466e83108506d03d5c31b1bd2387e83e5b070bda6ded9c37",
+ "sha256:28a23fefdb345b2d4d0ff2860571b5ff9a89a28b6a120f720e8fb0324d346626",
+ "sha256:290f66b97ede0e552e1cb44a0fd8a74f9753ee635b50830a0b122fb72788d015",
+ "sha256:2b9b1325ca1c2a9a2dbb6eb913ae563313f2082ae60b03210f7e83ee80712274",
+ "sha256:2bec13085dc8ef48a3fe62f7dfcacfeda2c785cdf19cc8eeda2bb9ed081da165",
+ "sha256:2cae5d5c90a62d9139c512a0cb1aad1d182b022b5740daea2617eb5bf7fc658e",
+ "sha256:2e01125896585139453cab8cb235893644d8815d7509520da95ae3ee8d1c1f79",
+ "sha256:2e62c569ec7531b679b184cbfe335c501c1d13c4b363560013019962eb630e6d",
+ "sha256:2f5b2a2b9811b853b39bfa41367c6d78747b8e3e80e07fc5a24aae295c1a4d7d",
+ "sha256:302f72413251c03f671e063c9414bed5dc8c927069e5abb69245521e51a4e81b",
+ "sha256:32a409be3190b088f960ac92bfedfbef2f86c49ff940765e1548177592d20026",
+ "sha256:33cadd956b667997e4de1635fce9541f2e8ede2038fcde8cf55aa14d571d1bad",
+ "sha256:379f8a75cf6eb7eef0af074b55f49ab73b868388a98de14646abcdfa4564bb11",
+ "sha256:3847e71a78cbbc1aff955dbbbaf2fff12153f611d3162c5beaa3395636cbc2f9",
+ "sha256:38fc4e4e4e084e0bd491949482527d406788045c546d4f8789e93fc527b91385",
+ "sha256:3a27ac6c780c8b8a1cd231b58407634cafc1c4cc28cd6c7141362df0f36351e7",
+ "sha256:3a48093cdb058a93af842ede9703520e810b05dcd0fc6d7190a06376c3bfb6bd",
+ "sha256:3e42265103fb385d8642a78672edf376c6f7e1d3598a7a4f9cb1278f2f6b5f6f",
+ "sha256:3e9a00d1c2c30936f7add097c41afc5da6556c580909104aafd382cac92a855c",
+ "sha256:40983eabefd13da003e68170928c7acc011f0d095eefce5871a3c71c9385fb9a",
+ "sha256:40bcbd9f94166ffe925811e730607385cec959f42fb1bb7dad83748680465221",
+ "sha256:41096ec0740a58dad03d3ae0c7486d306d20becefb13ceb1649835ab3eb64167",
+ "sha256:415e3a115c0d510e329020012834d1c0aa1c581ee53a218603e38abbc1dea70a",
+ "sha256:41e2d428110b408e963b6fb18f9bbf1f5c027b56bd4b498d54556476c0aeb1c3",
+ "sha256:424aa5657141d306ba9ad1baab4b2c0a0719040075ee6c66aee9bb2dea2b5054",
+ "sha256:42632b4024ab24a6b488f559ac851312509888b6b80ae2aa11cf29a646a0d245",
+ "sha256:45222d94ddd511536f3b2f7d9deae3b2339b4ce0f075f1ca25703b07cad9dd21",
+ "sha256:4736e6c87e603146d8949d8501da621ad20c31015060d3fcf95ace2859f3e3e6",
+ "sha256:48542c9acba9ff9450bd18d871d2c2c8787fdb283572b623d206f1b927cd7d9e",
+ "sha256:49fbc2682a9306135b7ec49e93f97f9c26689b9b7f96ed2742d8d6497e994d13",
+ "sha256:4a579dfb9c835f8ab47f4b8ed33440cbc75b806b73297208e6ec2a33e903740b",
+ "sha256:4b061064b4a2fe8598a466d723d43dbcd5a610a5d5cfe02fb6226f5c17349f75",
+ "sha256:4e11e885e0704be185867fcf71b904d8f65d7d6877bc121f69870b0d0479ba7b",
+ "sha256:4f4db7c7e954d289d71878938348b3d91b904a3e8210a11939359fb758a58e7d",
+ "sha256:527195c188d7d0af748cd48d220ab8cdc5cb99be3d49ac4d9be7324d8abf9bc0",
+ "sha256:53258656846f5c48996b882fb4b135885e088a3ad3d96b4bc0530f95124d1f69",
+ "sha256:545ccc14fb05485f48b4439ec35beb16d5b5280eb6c81c658bd4707a2a119414",
+ "sha256:5609efdb0d3c95499c00046bc53648b3482ec2175b5503d6e611b3f0555dc71d",
+ "sha256:5929d9df5e7e3379183be0e21f7d559618a5b61cb63280df6164019242e337ed",
+ "sha256:5a143e6207579de8baeded4eaac9134413200359f1969d636f0bfb98ee8c3c8f",
+ "sha256:5a721a98c649855963811b59b55755b30566e7f7fc40bdc9803d66dee9f811cf",
+ "sha256:5cffe18571ccc51d742cd08cbb3f8b756de9311d18c7ea98f5d92f37b8fb60c2",
+ "sha256:5d12669a2c419b0e8dc423d23dea24bb82f6f9cb829f32e04674b0ba40322a7c",
+ "sha256:5d582042c69857c364e8153de6e18e0da9b7b515a6a8113caf69a6ec8e0520f2",
+ "sha256:61116cec57ed69aebc70f37a545eec095339bb829efbdabcfb97c51e9536e158",
+ "sha256:611a51e61c92f62345a50b0035df6fc0d678f9299f33728826d831598862f59d",
+ "sha256:623c8799c17128753c65699f1c3aa32402657393a9ad6db09ed8b98ddf76611d",
+ "sha256:6374e9e382e5a98c9c5e66d41b357b470da1c54bce30f17f9dc4bcc58436cc1c",
+ "sha256:66299564c046bc7e0cc5de5106601eae907e9fa5904cd68a323380a8502f7861",
+ "sha256:69cafd61aea04ebb3502c93c2aaa568b12931ca0802231e0b5de76bf8b6e74bd",
+ "sha256:6a406d0b3cb207b0fa460ed4dc93e866f44f105da0169361cb18ff998a44c7f0",
+ "sha256:6ba4fe5bfbef6811a8e49b3719cde373ad399006c0c1ac184b7297116ecbba5d",
+ "sha256:6cd11e7550d89e551a87dcec30f04b1fca32e86b68708aa01a4daa455d8605e5",
+ "sha256:6e1eb8a4cbffd5553680ad96be6680e364710656eced73d1dc90ec489df599a3",
+ "sha256:6ea2f13dce778ca072ccee598bca46a092ce192e8fd907b6c1f0e52c800529a0",
+ "sha256:71532ebf30be0048a45559b4fab15333fbaaf9042f658e878d918ecd0cf09805",
+ "sha256:73fc05988ed20809450474ba760a87c8ad4e455fc09783c02195e56ec634b41a",
+ "sha256:75cc6569e86be5785b6188ef1642670c6adbc984e81ec35e224842ecd9eefcc8",
+ "sha256:773062aec2f2e56b2b22d37054123f0de8a22a4688a0c3376c3fe42685f975cf",
+ "sha256:7ae4949f212a53b007dbc355884fda122545c5764a54256c9217e419a62a6559",
+ "sha256:7b2bb7d703bed7ac893bf7f40d97b5d9279d35d2ce460624ca28929eab0d5a3d",
+ "sha256:7d0f5976aa2701996f759b30172925829867547bb073af0ae67d1307a0f0262c",
+ "sha256:7d5a748d12dd9b535e0a130f60dae9ddf0adafbabe61e7864f55c7436c84547a",
+ "sha256:7dd624c1eaa629ad44b59a1a0145fdf2d67895592dce94c9358b938b3d075e65",
+ "sha256:7f75b9b9fec2a9c6b18095c81865580e795b1441c429e42d22fcc82a77f40039",
+ "sha256:83e3a51e7933db700a0da0db31849db3a24022d9970da9bb73001e1d0326fd92",
+ "sha256:8499d464de86fab0f102313cce32a9bed9ab1f06ec813cf025cb790964fbb765",
+ "sha256:869dfcd4d381cb0ea87085cc4f011b9171b494ef21e76ad8665f6d5e2d1dc8a1",
+ "sha256:8753b8d51dbc86fd335ee31fcf7f3658e9f5c016d4edfb23f76ad295f4b8c9d0",
+ "sha256:887c021d9a977cff89cb273047c1352997b772a8908a25c21836861f69b92be1",
+ "sha256:88e719b9437f148f7e1465df845c758dd1598618cbea3a2fd1e61a715542f2b2",
+ "sha256:8a330c0ee5fa318c7b5cbbaad882baeca3f570357e7eb25ab34bf31008150758",
+ "sha256:8db38ff3fb7aee7d6a82ae4da2eef1178656fe1216841fbd24870062a9d60473",
+ "sha256:8e49a646acfab83c68974f4aa1d0a2acca9e88d7d627ae0fc13201b14b76d310",
+ "sha256:909f4e927bb051f7740d6367285fc60cdcfdaf0258c2dba4ff5ba7eadadc250c",
+ "sha256:90f709b9accab6b2e4d14f5c8718203877a0486bcb3afd74d8b539ecd1e961d4",
+ "sha256:92d96586376fb79a33474797186bf993250152ee5c32650b67db78d54b92e6f3",
+ "sha256:93476b6514b373fc6ca67d26c442784f7807c86f00635bfe79f935c3eab2af17",
+ "sha256:97acecb11cbc411473f15b8d780df06d7a9f3a2aad9aca78364f56640c8fb70e",
+ "sha256:97ce49699d87ebf8aad631b55d65b33219a4f1bfefbbf5bff19dc9af160aeaf9",
+ "sha256:9bde9ae026a55b9a192078dfa6e27dd0ca4a050171ab6272e92f97b757dfdf48",
+ "sha256:9e67324961ac9bbe616cce5100514d2e34d88665aeb07071e8b16eac55d06d94",
+ "sha256:9efe56a68179f3adc4de41861c9358931db03837c48dd5e1c78077b84dd07f3a",
+ "sha256:a1932d7ce78a561367512c594fe66eac2b2ec9b9264cfd9b5f950622f4a116e2",
+ "sha256:a1cec0f99b9b914d39176347a93b7610dc09324491aee1cbc57cd291a41a1d55",
+ "sha256:a2e3f70673a1d5b82f38255f777d26cd855bf2092b1436c4867464a7892f9238",
+ "sha256:a43b3bdf11e477dc7770609d3477316f974354dfc8425d596f64f471cc8daf6e",
+ "sha256:a5c18810318303ce9afb3f95e2ddb54834f96fa699a8600433fd5a93dcf44c56",
+ "sha256:a7eb78ba28b187e1e9203a55c60fcf70df2d22cb205fe6d51b9383d6097419f0",
+ "sha256:aa633613ff907ea91b9b0489a1f0da1b8725d8c6ccec6b77e8a1c9c235044bb0",
+ "sha256:aa9fd1ee2a5dacfc41039ed49ffeeacfa75bafbd255b69f3b578e11897a0e623",
+ "sha256:ace1d2c83b2bd24db5940600541140e87a325e119cb32d5fa9ad720d7e76648e",
+ "sha256:b1cc980905221a5d8b3c476330730b3adb40ff80add71ffbdb6215ba055656f1",
+ "sha256:b37772102d44bb6628186accca3a121b1fa3a6b3d97518a8c29a5229ca4c0d0a",
+ "sha256:b3ff39654f0ce6ebd4db154211136dbe7e8157bcc3bed2344c87f32c7c6ecb6c",
+ "sha256:b477912f42c5c33405a10c759d22f80cf5af043ae02d95b9d8e5e5bc555739ed",
+ "sha256:b49638355ea3bebba70da783ccbc630fd72afa16bc46c54474bfa1f9a915bbc6",
+ "sha256:b4fc6b03b9d9d90557274f571ab30e7fbbfc527955536935d96f98b6817a86e4",
+ "sha256:b50343241eb69fd85f7791cf8bcc7b1c4729826b7d59ba2f6b27db29638fa745",
+ "sha256:bc8dd3d9c93e70c3df974a201ac2958b6d77b465d813c51d1f15fa8e645763ae",
+ "sha256:be5346653c0b0e34be96869ff9dbeba23860156f89a2896a64c64fb419260cb6",
+ "sha256:c00e26288784460885fe76e4d4b293573e0f791f52e6d60e27b42edf005922eb",
+ "sha256:c1b50797ac246bb2942a04b6c0f69af0667aba7cf7535f39bbb1b3208fd5d128",
+ "sha256:c34ca1dc41bd86d9ff830d5bdf4e4a752bba6c54f7d2707027ce0eabd36084c9",
+ "sha256:c55e71a9b1db1f107efb60da49c093689b74c5c31a708e5379e2fd9439d4fbb5",
+ "sha256:c581b1d68b3845fb86c6b2983e755b29bf001461c59fa411d2c26a911b6559a9",
+ "sha256:c59e4265608da6a041f54646ecc0c9ecdbb19aaf14c4c684bb6c2114998cc415",
+ "sha256:c5e7ce578aa8a80910a72a8ca0bbea3baae10100827249001999726a788456d8",
+ "sha256:c66f858b82497173f73366795fc6ee8171620e75a338506d6b2e7bc16f5fca11",
+ "sha256:c6c0c13128a32eb04a51357e56a094e13aa8e6d3d1884de2e9ae923f6915e1a8",
+ "sha256:c9389b3784b56c58d933b5e0aecdf28f901b073ff385358d8a7d40907f6e14b2",
+ "sha256:ca0ec532ad2f5ba1e5ec120ac157769c57f01855b3d8bf37213f5d88abd9ba0a",
+ "sha256:cad7617727a96d189bd6f979d0fadf765198c7934e85f4edaba9bf3ad919a300",
+ "sha256:cae82b5ca24b0c2beedb269f6e2a96f466acd926879ab00ae19f1a65cbf9ffb0",
+ "sha256:cc669256d28736f7f3a149df5c380c50ace2692ba3e62203d10656fade4a2145",
+ "sha256:ce1f220114959941170e22b8ad44279f6dee2dcef7591814d01ae805dc058889",
+ "sha256:cfb398886a7eb4c719161c3efcff2a1248febc53a4d8e5072d2d8a87fed84ac9",
+ "sha256:d077f21f4b16f0471353883748f126f62038760397c107bb9fad2ca94dc0dfb7",
+ "sha256:d0c5c362bc94f1929dc7e96e715bbe7bd17037f802e6d8f0d1545df9133c0559",
+ "sha256:d2765c18ce303149ee804b1f3dad11232726dd0a702d73a15cf19179ac8cc962",
+ "sha256:d44442effeb8781f392340c5dc8c6716fba41dbeacb82fd4c0f09026fb5ff682",
+ "sha256:d85dfab42dd672f87a7f76e9de7172962aee69fa12044f0d6e1a23cbd53fb80e",
+ "sha256:d97c5227621af74b111882a290b10f371780a38eef9d9e730408fba2259b52fb",
+ "sha256:d9a0d12846d6ce434fb3857918eef4315ec9b4769deb020c75828798614bfcfd",
+ "sha256:d9b3e7d71bf6acff341233417abbdface29c647e3113892d9aaedc02eb4aa2bc",
+ "sha256:da707f14ea3c35ee463d50acd596d6488e4b2b4ae7cf77a5bf93f55c023d63e8",
+ "sha256:da85db328e507da922d586c3c7416ec360ec22e9cd9e0700691afacde0c81f53",
+ "sha256:dc205732d593118cf701d986f40e9de7801bb2e371cb189ddbda9b7348f4d97e",
+ "sha256:dc3a44689eea43eab836e5c98a8ab015dc2419987d1ea6eafc7c590cdff86bed",
+ "sha256:dd5e90f34cffcfed97f36cf066325773d2b6021c60c29942e53a18b028501b1d",
+ "sha256:ddcf547bea2aee967d6a77779376a45e77e610e8465147a1f3d7e20d539d6e32",
+ "sha256:e477aca0bc0d19f3b4ae9e4f2a1cfd687c31bf772d78734910658186b40b2477",
+ "sha256:e8b17e23df3e827a69d25af70990ca2420e92668aaffaeeb3cd2351d7916a023",
+ "sha256:e99e09ab7741f1281e2677f4c0058c7f5267d182530b09c87e4f6aa26adf3887",
+ "sha256:ea2c01cdb16dc12156e455007c406dfaaece0c89aa4ba0e3b47586779f951d41",
+ "sha256:ea6b1e9105b4b24a34c722432d9fb578f9ed83af21fa1abda639011e0f22bbb6",
+ "sha256:ebd054ad1737a68fb7c5c073d405cef2b88bb824e294de3b4a4e995b47f0e376",
+ "sha256:ec295280f4b37769256da025acf5890370355ac589c27e89caae0b5e9eedc702",
+ "sha256:f6449672f9c93316deb5e2839e18931f468670e44d5bd9b1301a5a9655d45c07",
+ "sha256:f683dc6300317700025e41d89a43e0276692ded16113a3c43eab704d605c58e5",
+ "sha256:f6b9d2aad499c769ee8287609ab0e6de99d8bcea99c6e6c2e64945259fd52fb2",
+ "sha256:f8b9c8ceebae6387d0dc77f7f4dbbfbfc962dba2efbfe6877486075a480726b4",
+ "sha256:fad67b12ffe0f71e02b4932b04883cbc76a9072bbd30731409d3523cf058b011",
+ "sha256:fbfb70ba01355251faf6b293171df49f73a88a1b6494db109ffea85442574458",
+ "sha256:fe91993149523aa59941b9e3c90e2eb45f57ad014697aef6c8b13339a59c019e",
+ "sha256:febd35ef45f603c2d74b74655efdbf45e14f55fc0aef4ac82b663ca829b283e0",
+ "sha256:ff88a92cafde90888511242d1c54afcc1a8adbb6dc0a88fa7f87e29e92400d4a"
],
"markers": "python_version >= '3.8'",
- "version": "==6.1.1"
+ "version": "==6.1.3"
},
"mako": {
"hashes": [
- "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9",
- "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"
+ "sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617",
+ "sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27"
],
- "markers": "python_version >= '3.8'",
- "version": "==1.3.12"
+ "markers": "python_version >= '3.10'",
+ "version": "==1.4.1"
},
"markupsafe": {
"hashes": [
@@ -1607,61 +1665,83 @@
},
"numpy": {
"hashes": [
- "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2",
- "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d",
- "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1",
- "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b",
- "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd",
- "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077",
- "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a",
- "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e",
- "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277",
- "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6",
- "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75",
- "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7",
- "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1",
- "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9",
- "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21",
- "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca",
- "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0",
- "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb",
- "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d",
- "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75",
- "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74",
- "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf",
- "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0",
- "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8",
- "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af",
- "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a",
- "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4",
- "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22",
- "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3",
- "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1",
- "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b",
- "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1",
- "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373",
- "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95",
- "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6",
- "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09",
- "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9",
- "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438",
- "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2",
- "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7",
- "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace",
- "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3",
- "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2",
- "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107"
+ "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c",
+ "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be",
+ "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7",
+ "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def",
+ "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2",
+ "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0",
+ "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6",
+ "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551",
+ "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f",
+ "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159",
+ "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6",
+ "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0",
+ "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5",
+ "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10",
+ "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a",
+ "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a",
+ "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c",
+ "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff",
+ "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33",
+ "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0",
+ "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85",
+ "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034",
+ "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0",
+ "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a",
+ "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4",
+ "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c",
+ "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec",
+ "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58",
+ "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997",
+ "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90",
+ "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204",
+ "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e",
+ "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211",
+ "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435",
+ "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104",
+ "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321",
+ "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058",
+ "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0",
+ "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297",
+ "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07",
+ "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694",
+ "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b",
+ "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034",
+ "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3",
+ "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231",
+ "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc",
+ "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d",
+ "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4",
+ "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653",
+ "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e",
+ "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b",
+ "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9",
+ "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034",
+ "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958",
+ "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832",
+ "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563",
+ "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034",
+ "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841",
+ "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae",
+ "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469",
+ "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c",
+ "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891",
+ "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5",
+ "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09",
+ "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e",
+ "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab"
],
"markers": "python_version >= '3.12'",
- "version": "==2.5.1"
+ "version": "==2.5.3"
},
"openai": {
"hashes": [
- "sha256:4e205548acd4304f235b86202269912e55bc88270b15d2a051fa2b53b90343a6",
- "sha256:b3a1a7ad974092427ccb46d89f8852bdb67866680bcabeecc3ff5a3fdd71b15b"
+ "sha256:514736aa1e4ef1033c1209ad53897392845ccd4f2c4fae6413b2cf5f91c2c926",
+ "sha256:6138a5a1333a1be9e4d1edea2d160b311542787b029543f87de4961c66358d16"
],
- "markers": "python_version >= '3.9'",
- "version": "==2.47.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==3.8.0"
},
"opentelemetry-api": {
"hashes": [
@@ -1817,83 +1897,74 @@
},
"orjson": {
"hashes": [
- "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4",
- "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62",
- "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979",
- "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0",
- "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d",
- "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a",
- "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd",
- "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce",
- "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1",
- "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180",
- "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980",
- "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697",
- "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e",
- "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1",
- "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09",
- "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd",
- "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470",
- "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b",
- "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877",
- "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb",
- "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd",
- "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe",
- "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97",
- "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c",
- "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5",
- "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021",
- "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362",
- "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206",
- "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4",
- "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218",
- "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9",
- "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f",
- "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4",
- "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02",
- "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be",
- "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972",
- "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586",
- "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2",
- "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124",
- "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa",
- "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47",
- "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c",
- "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244",
- "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13",
- "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10",
- "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677",
- "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48",
- "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4",
- "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624",
- "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49",
- "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0",
- "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b",
- "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c",
- "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2",
- "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db",
- "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92",
- "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94",
- "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e",
- "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61",
- "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882",
- "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff",
- "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254",
- "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f",
- "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32",
- "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff",
- "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673",
- "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291",
- "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0",
- "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c",
- "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9",
- "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f",
- "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e",
- "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7",
- "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81"
+ "sha256:010811c1b69773450a01cef97727a67b223242f350b77d4ca000e59a9ef2155a",
+ "sha256:01efac2074fffb4cb1ea3fab7861e9d0f2a26913854a972f5ac760525dbdaf6e",
+ "sha256:03091c8a64db4be38746597ceea68f33c238e27acd9bfe99fb59420224ae7a55",
+ "sha256:08231552159be266a7269555bd9f7c016aee7d9ad6dab06eb58796c5ccb7101c",
+ "sha256:0b1ac5bf6609b2716c7954011c5fef6254922df029f45d032ee4ebf5d363cbed",
+ "sha256:103b5db66aa53c1f9e88c2524be4f383e831ba7dfd5f9f5af6336a177c622f11",
+ "sha256:1192a7021b6d071aaf909864f6e924d6a2675ca360485b972b8401749311750b",
+ "sha256:11edb4660a6680abee9788a3a9072208a2c96538cc1322bd79542065229d8e54",
+ "sha256:18a87929f31d94a77f7dc93cf527e91f39ce7fe7813d588a4de2507efd32a387",
+ "sha256:1c680706fc8396d95e7c4c1f9482563f552137aef91b57237a3ad5aaf64629df",
+ "sha256:2b7bcefb9f40fa242fa6b06377232c048e655747790829609168c01162f60578",
+ "sha256:2bb3ce43203936072dd8b4917b01d3aecfc02329bfb42510cb7cfb24708adc9c",
+ "sha256:2d3a9da945a4d96ae758fdaaca56742e6b73b6fd554c5d8876f252a6dad70b83",
+ "sha256:2eb5c56e534127b2b8fa38d2363c8b1b8190367ee0d1d16c041517d880843b94",
+ "sha256:31ed278a36304390adc3eec5d7f6fd593a7c3e99e5a06cd07866396c4b1b4710",
+ "sha256:33efefcf5d88eaf400b47e2eba02f91f319bb9951be61ca500b7d536d3f2079d",
+ "sha256:3bb17a06f9bd15237b3216c044209fe92597379124018cfc196fbb846cde64df",
+ "sha256:3dbce9b6b3074b31a5d5dd322a9c4e5b16f206091ece4194c2e36952847a105e",
+ "sha256:40f92192227505acca4e2533ce565f8e6b9535f7d0d09b0968452f18b7376b38",
+ "sha256:477ecaf6b9f88f873341b91fcc736119ca81b5e002a9f7f308ff5b4f2ce2a70e",
+ "sha256:50fae885cb073eac7556353ff3df93312b0d5137b0a5056b2bb63f97ed9a93c7",
+ "sha256:532ff8cd4bd59a327a953a7dcde922c7fc25b85e29721bb8633265430d3a3873",
+ "sha256:53c0c474a9d9aff9aebfc0c88de1f28f843d940e6e3a80729abdf6a20274356f",
+ "sha256:58c58e1de0006ffb580368d6793c36c7b0b021db066479cf281bf5061e732328",
+ "sha256:5a0fdbc216388f653d3752ff310e710f59253bd4ed6a2bfb3f4f06b84714bbd8",
+ "sha256:61318b6de893c7a9d9f3e5ecbadccbfc26a7eb417ccc7bbf0771de3b4d72f868",
+ "sha256:644d005bc82f917337a95ce270c9f6f92f9834c2bed7b1477572f8db00784222",
+ "sha256:6a2a79c89984dc719817d388c8709e0efc2a2795a934eaa746b4882eb6045adc",
+ "sha256:6a31348d7dfa64cd9c78bd1f510ff44c48fe64d71094e6b90e364dba3b55949e",
+ "sha256:747843254519dd43b93eee3153a19e5a509334320c4d2f823ec879232db5c796",
+ "sha256:784106539f4b9d4b930e0b4eb8d45168507dae001945e71b4675a367f1e5e806",
+ "sha256:7c2ad193c8004254f34b499f3bd2c80f043d10754aff2b38f93da574f4883f98",
+ "sha256:83445adc40cba26d6d621185a45128ce455b766af368cad2ab64b970603a7978",
+ "sha256:859fc4196855890150bb08e649b30d2c93b249b3e3edd0d3bb2231abf8aa8adc",
+ "sha256:8c3bb86dd10f39b3fbf434b7d5dc7cac77d6fc8ac572ae30a10731ede2c4b647",
+ "sha256:8e29957429c35bbb5a185a119c523aa2428b7bbf1a293724c7b9375ed8f892a3",
+ "sha256:8e386b0bc0ddd7cd2056f884b5a0af33592bd01ac66a7ca4b42a65a7e7774a13",
+ "sha256:92ffc09e07233a6ab6d4e067f7841edcbcc134cb4812155cf171ea5255a421d7",
+ "sha256:9a36ec60f1796f9a3f13e3b98390295e17a1c7c10155b448d264098bf9ee5900",
+ "sha256:9caf3d09f47c3c70c4451ada20ef9bc4a4cdffa26f49862cf0a253b329aae2d5",
+ "sha256:9e6fee342a48760e854d743e7a81534d8e2925a6f46e09f750cf56b50fd1de5d",
+ "sha256:a15f9a891bce5f5cc5d210e3ad8614d4d1b489a56448c099d6d2a7168b2d954a",
+ "sha256:a696529ec96a90d9a5f9570207efe403c8b08f8e4aa2783ee3403511e2fdfa10",
+ "sha256:a6cf4b18e7de173f209f2084ffbd736dd72389a396326ee80a7022168be232e5",
+ "sha256:a791f793b287bbc135b8e87c34e35c8bfc693e2a8a620fab1ae682b925f9a32e",
+ "sha256:a94f0f0c6fcbb2b5bd9734c57a489c7584a732bbdf04a39e8c83b861e9d03e92",
+ "sha256:aa3e43a6846e91d7bde3d5a9c66090fcd8744f569a9b6cffc5e1ca38f6a461c0",
+ "sha256:ad0422b92d5195443a39f80c3bcf731cc2e00f153bd32063a47b73b057bd0f03",
+ "sha256:ad29eece0c601737f2a60edc2752a84e7a0785df3efb62e3012834700a5afe0d",
+ "sha256:b85931be5b6763c31283805c9bdaae1ca03ad9f6f12a15f1cbf6745b907932c2",
+ "sha256:b9dca132b1fda5565088e65a6b6e742285e0aeceb6fae549fa8863e16c7d3998",
+ "sha256:bc7a872f03522d90e0429e6c0c5cd23084f767bedcb4c58048eec19294613344",
+ "sha256:bd57d79aefa3f84eec851d6de7a366795b9345cfaf17f82b4820430a7a5fa241",
+ "sha256:bf44e374aadde77b1f6109f1030be51433eb61984379852766b6f4e187db7b1e",
+ "sha256:c6b11be792c3d2c6a4be2af4ebf97a68d0bf5f580aca6e86a418a354f6cc846a",
+ "sha256:d14203fb1aae2ad9b3d52f8a0e82aeb10197ef1c9bc61da7f358bd70b00123d5",
+ "sha256:d39f3f5c3927e2dc0913fe5bbc1a2f6b1b9d1bba1de6358340d0ad0d0c00ca92",
+ "sha256:d8e78d3d93705e3d27cc17cdb209e44d7a8ea203010cac6ce9c7ffc1ae1996f1",
+ "sha256:dce0166feb0a737ab84f598c9a338cbc0b764a036617aa686194f53c7eba0c3e",
+ "sha256:e4ac5059baab4b3acbd99485de019ff8cda0fdf34b61fa74f7197a53db78bfe8",
+ "sha256:e9683ee9ea0659da64f36574ef675b8a86330c34c19ea75db1fb93c3ff99e0ef",
+ "sha256:ed4ca42bd55955aa34deedcfdfd0e0c31abf51143aae158ae2bc3520b626e517",
+ "sha256:f06dd838d1e07d9b1de0932ec0485ec92c4d5f5d1ad4817a656268c3e88be1e1",
+ "sha256:f3c0683136acdc29afdf88a5bc2f7d3d0e34087788d1d63c0144b805a87a196f",
+ "sha256:fb2539159dfe8d371914f354360fa50e4a577cc89222a3828b9650a5e5040252"
],
"markers": "python_version >= '3.10'",
- "version": "==3.11.9"
+ "version": "==3.12.0"
},
"ormsgpack": {
"hashes": [
@@ -1961,11 +2032,10 @@
},
"perplexityai": {
"hashes": [
- "sha256:2a1c237500a784e9c80f8cd3f3c4d0ee2a73976ae75dc8f777f7063fd9c34ac3",
- "sha256:4bae8394e795746071e5955ede53cec80b567cb60b9b8da72a64362d13375052"
+ "sha256:bff26b7d4af6590813cff40234205fddc4b919c2b3f9f06307817ce68db904b9"
],
- "markers": "python_version >= '3.9'",
- "version": "==0.42.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==0.43.5"
},
"pillow": {
"hashes": [
@@ -2334,146 +2404,146 @@
"email"
],
"hashes": [
- "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba",
- "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"
+ "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73",
+ "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
- "version": "==2.13.4"
+ "version": "==2.13.5"
},
"pydantic-core": {
"hashes": [
- "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0",
- "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262",
- "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda",
- "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0",
- "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e",
- "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b",
- "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594",
- "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29",
- "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2",
- "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c",
- "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d",
- "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398",
- "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d",
- "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3",
- "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f",
- "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb",
- "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7",
- "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5",
- "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9",
- "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462",
- "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4",
- "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b",
- "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d",
- "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df",
- "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2",
- "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0",
- "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519",
- "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd",
- "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7",
- "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac",
- "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6",
- "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565",
- "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898",
- "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb",
- "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928",
- "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6",
- "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3",
- "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a",
- "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596",
- "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987",
- "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e",
- "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d",
- "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712",
- "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008",
- "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd",
- "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1",
- "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be",
- "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea",
- "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292",
- "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33",
- "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3",
- "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4",
- "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b",
- "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826",
- "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac",
- "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7",
- "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d",
- "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf",
- "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4",
- "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc",
- "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15",
- "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3",
- "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b",
- "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914",
- "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04",
- "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c",
- "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b",
- "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9",
- "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce",
- "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4",
- "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a",
- "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f",
- "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424",
- "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894",
- "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9",
- "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76",
- "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201",
- "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb",
- "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109",
- "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4",
- "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848",
- "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526",
- "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0",
- "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01",
- "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458",
- "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e",
- "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba",
- "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a",
- "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39",
- "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c",
- "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000",
- "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b",
- "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf",
- "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4",
- "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd",
- "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28",
- "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9",
- "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30",
- "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983",
- "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1",
- "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76",
- "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5",
- "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4",
- "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7",
- "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c",
- "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066",
- "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3",
- "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02",
- "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89",
- "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50",
- "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76",
- "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49",
- "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b",
- "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d",
- "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7",
- "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4",
- "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c",
- "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e",
- "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff",
- "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"
+ "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942",
+ "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821",
+ "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5",
+ "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c",
+ "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184",
+ "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2",
+ "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc",
+ "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5",
+ "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0",
+ "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931",
+ "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e",
+ "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25",
+ "sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d",
+ "sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6",
+ "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47",
+ "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038",
+ "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8",
+ "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b",
+ "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a",
+ "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e",
+ "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074",
+ "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0",
+ "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433",
+ "sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f",
+ "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034",
+ "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21",
+ "sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736",
+ "sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0",
+ "sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7",
+ "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c",
+ "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4",
+ "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c",
+ "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c",
+ "sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069",
+ "sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb",
+ "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061",
+ "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29",
+ "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3",
+ "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa",
+ "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e",
+ "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38",
+ "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f",
+ "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b",
+ "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8",
+ "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e",
+ "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c",
+ "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be",
+ "sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6",
+ "sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793",
+ "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1",
+ "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b",
+ "sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64",
+ "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f",
+ "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761",
+ "sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d",
+ "sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168",
+ "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869",
+ "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111",
+ "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5",
+ "sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b",
+ "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7",
+ "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129",
+ "sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e",
+ "sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655",
+ "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c",
+ "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec",
+ "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689",
+ "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f",
+ "sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf",
+ "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea",
+ "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9",
+ "sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464",
+ "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519",
+ "sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b",
+ "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f",
+ "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee",
+ "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a",
+ "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e",
+ "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6",
+ "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2",
+ "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f",
+ "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a",
+ "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f",
+ "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a",
+ "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47",
+ "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda",
+ "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0",
+ "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4",
+ "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d",
+ "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3",
+ "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2",
+ "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355",
+ "sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461",
+ "sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed",
+ "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f",
+ "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5",
+ "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2",
+ "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829",
+ "sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0",
+ "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266",
+ "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575",
+ "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290",
+ "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f",
+ "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62",
+ "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f",
+ "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a",
+ "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7",
+ "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed",
+ "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f",
+ "sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1",
+ "sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615",
+ "sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8",
+ "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3",
+ "sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a",
+ "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff",
+ "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084",
+ "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0",
+ "sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3",
+ "sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13",
+ "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9"
],
"markers": "python_version >= '3.9'",
- "version": "==2.46.4"
+ "version": "==2.46.5"
},
"pydantic-settings": {
"hashes": [
- "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440",
- "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f"
+ "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42",
+ "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117"
],
"markers": "python_version >= '3.10'",
- "version": "==2.14.2"
+ "version": "==2.15.0"
},
"pydub": {
"hashes": [
@@ -2485,20 +2555,21 @@
},
"pymupdf": {
"hashes": [
- "sha256:44f0973f5e5edbaec95bc34b64e71d1959d4ee90b1328de1b4f4f5b4fa78673f",
- "sha256:47a5c29ed4eb0744de9c4e37bb49b1259b18d4d75fcc8a7c130f7c9fa15956f6",
- "sha256:4d61ec323a706e153a12e262e51febfb43eeaa20977785ace135d18d48bcdc83",
- "sha256:4d692dcf44d3566ae96bc6f6346c6ad432274a29ba617bf7a9fe18009e24adb4",
- "sha256:74c6d00ba2a9aad3a635db73b07c15db462b480741d831a34a75a56535ebc22b",
- "sha256:892b89ba88e8f98b53133b62877a9dc9b5e7dc6a4aeb837b612db56a8d2e03ac",
- "sha256:b3e1399c7a64c6914239116a369efcdaac4cfb9e838bde2656d7accc4a85c72d",
- "sha256:caea2b3b67347fd79e5d15ed7929b0e886aac594ea228073b6d39de0078189da",
- "sha256:e01e90fd86abfeb37ceb921eddb951f988a11d45ff6ce6b7664f2039849068ec",
- "sha256:e53f3567403a92da15caa9e7ae0164327fff48817e9f40175367fb9de524258d"
+ "sha256:2e1b574c0fd2cb238021033fd3c0f9c4388816638df064e4bfb56d9d81736dc8",
+ "sha256:3050a233dde1211efe89ada74e2add6238436434159f46097a1423aad2842545",
+ "sha256:397d6715c1f0df7548a92d0afd8ce370fc48fa47aeefac16be2bc04a16a8227f",
+ "sha256:530ef543a3885b3b81cb72a854e7c5a625a9233201221132bb6c31698c6a2bdb",
+ "sha256:5e0be7908a715aa20333caddd73f1d6f01e4cd0c26e869fa2dd0b7f344da2249",
+ "sha256:5fc315b425ff1f7afdd1ea2f348205cb19b806767daae7ce4d64115799c2bae1",
+ "sha256:7113846b35dbf0a033f088e4f4fb543dabeb4b0b12c112966a1ca1ee2d5eacae",
+ "sha256:ebd244918798502d7b4504c90410d1711a4d7675a32584ca30f1bab419ecbffe",
+ "sha256:f89fb2d86d07d643a269f17a093105057e20c79c1d06c103b53600067b6d2b01",
+ "sha256:fd481ed48bef56305c41fb7e05a055c03345c899c7b101dad086258b438f8168",
+ "sha256:ffe91a24edc75c80da2a4b62f50fc0f54632d34fc8fe4cbc48e5c7ff07cf8fb4"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==1.28.0"
+ "version": "==1.28.2"
},
"python-dateutil": {
"hashes": [
@@ -2520,11 +2591,11 @@
},
"python-dotenv": {
"hashes": [
- "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a",
- "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"
+ "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9",
+ "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35"
],
"markers": "python_version >= '3.10'",
- "version": "==1.2.2"
+ "version": "==1.2.3"
},
"python-jose": {
"extras": [
@@ -2645,123 +2716,139 @@
},
"regex": {
"hashes": [
- "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0",
- "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6",
- "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62",
- "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af",
- "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc",
- "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13",
- "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd",
- "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951",
- "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc",
- "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511",
- "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12",
- "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518",
- "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db",
- "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae",
- "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009",
- "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986",
- "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1",
- "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a",
- "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2",
- "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0",
- "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78",
- "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d",
- "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4",
- "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0",
- "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11",
- "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52",
- "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e",
- "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902",
- "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11",
- "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6",
- "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba",
- "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e",
- "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac",
- "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939",
- "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb",
- "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc",
- "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095",
- "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b",
- "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b",
- "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220",
- "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c",
- "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae",
- "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3",
- "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44",
- "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665",
- "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5",
- "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97",
- "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218",
- "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864",
- "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e",
- "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4",
- "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda",
- "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459",
- "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18",
- "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3",
- "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5",
- "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a",
- "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035",
- "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa",
- "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5",
- "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78",
- "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20",
- "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a",
- "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a",
- "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a",
- "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965",
- "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5",
- "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797",
- "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276",
- "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c",
- "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547",
- "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9",
- "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d",
- "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1",
- "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68",
- "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a",
- "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd",
- "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c",
- "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6",
- "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82",
- "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7",
- "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15",
- "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e",
- "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38",
- "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96",
- "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2",
- "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8",
- "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732",
- "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966",
- "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053",
- "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3",
- "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0",
- "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f",
- "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e",
- "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327",
- "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac",
- "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6",
- "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2",
- "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a",
- "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435",
- "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5",
- "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d",
- "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312",
- "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b",
- "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40",
- "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974",
- "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404",
- "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff",
- "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf",
- "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175",
- "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da",
- "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d",
- "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1",
- "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2"
+ "sha256:01bdce6a372efd5ae3d8560cedbc691be259a50206564bbaf04008b2937721ab",
+ "sha256:0540de6e7917f89acaf9771bdcd6fa7e505c67416d3b283e52ad4ab25399c6d6",
+ "sha256:05e9f7d16b42686fb38b1702071a7359469ba89e9d516e2ba5228e077dcac524",
+ "sha256:0b1ba3aaaf5776de473ee16625ac60ac195abb0343afb273575a8201d99be089",
+ "sha256:0edd12c8201222f58817689dc61fe44893f3f2f2aee530b211dfa92af84df9cc",
+ "sha256:0ee4721472e00e96b3cceec545c9867f91815f628f6ea304ae6cea93a7e4e7ae",
+ "sha256:0efb99024ad5ba9198ffa816156b3319c3164b5a8d1e35940d92fdc9158b8d9f",
+ "sha256:107319f437fc382e1e445d0abf8923db07f76e5fb3245ac5b09604e5dc8d4f63",
+ "sha256:11f9b104fa7b23f736b50fe1980a1422eb4641865e82614a25413deabccae575",
+ "sha256:14bf4a88833c12a990dbf5cca77eb293401d60878be7d566a2d5096fb0216c27",
+ "sha256:14f8969a92ed847f78e8eed81c6a0384a9e8058a56eb659f83ddb59879c2e4e3",
+ "sha256:185c1ae881856208dda05708b6c908aff76878e59c998c8548d365c1bbcaf1bd",
+ "sha256:1ba1dbbb93c5c5629c1861763aec5bfa9f05ad24ef450694130e25029ce7bc36",
+ "sha256:1d9148e47cfa1a067138867996b1d5d825de0132fed8dac3c92eebaaf312d280",
+ "sha256:1f22e0d21ae7016c77175c139a7fca465b988efc1280df4816c79752068d9e2e",
+ "sha256:1f975a75dae06e88665e4a709873d936a7e8f9445e3d354b75de0e735d28cf71",
+ "sha256:233662cf8cfdfe3c0e58aa8f7bbefc579b5be0ac34546f123c159804179e8687",
+ "sha256:24b4bbb65ff2c4e8c552c93c342bf5ffa0ad162d963d96bac7c1883c20e1b34e",
+ "sha256:27f0809798071f56fb1bc536bb93714a95e8ed2ec0dfd869f095deebb30fd11a",
+ "sha256:294ef8fb58a45f912513692380f366ca191940de5b3b3de5308ce2e8500d8ea4",
+ "sha256:2a1269856278ae8bc78342bf20191c1f10638d2c22df72364d2fa02d70d49f35",
+ "sha256:2a36e654181ecb996241bae256d28f26b03d019d6ef65c61dcf44396ab07c548",
+ "sha256:2c71da070224baf426850d9eab23ac797d9859b1841dbda43012c01b69697803",
+ "sha256:2cbc83154c8b0201ada07bc5d6e37106df6325f2fda60d73228e2e8da7433cae",
+ "sha256:2d25e41851e41539898116ac760fcc856e2c1047a843a336b19e2c8e4a43ad16",
+ "sha256:2ef9a284ec658d48ec57edfba0944d1582532601472f695b799ba08d37fd6544",
+ "sha256:3077ace9bf59f8513c8471a817a5af63699987dc024f535c1eac3447a4d70211",
+ "sha256:307dbd844f678de48ea74cdc909b007729c50e6be7f182bdf6a1df6732374c69",
+ "sha256:32a32d2664e602b20e4b9c11234cb32f2e985323e453b2b396ea4ae686d82052",
+ "sha256:3367d5eefae493ac2a1586ec11cc8213c3305528ed3f8e19d5c3871cc01da6ce",
+ "sha256:33c2860b73ea342c0a42bee9ebe3b3a0de3d68c580c4dcb52241cf4b6663731b",
+ "sha256:33d3a772ff62c882a5d1402045e17c0199f33b9b173139071c4203e7e0292420",
+ "sha256:356fc21b4c313decb3214a4306bb5bd0623dce4a8d03d0d5ada6fb0b6a5b93b5",
+ "sha256:35d48ce3dee087b63b15cd0a7a3110d0a76c29edbe1f2ad0520b8c4adb7cb596",
+ "sha256:39d8d5490ba4b8f26ef2aef72b775b1e7fc1a5ebaf28d11e637eb27b0b6a3048",
+ "sha256:3cc1a82779315f7b2a4642d5028b39057838cd4c0415744d4e53c8b512352141",
+ "sha256:3d75065d9f6ed1afb2a41588def408cf442cee65b3651cbd7e86650146127bb4",
+ "sha256:3de4eab8c763393b75bbb26f81934ab2cc8794f48f79e90622e3ab7ea57f3d14",
+ "sha256:445623b1337e971ccc571d3642aeb3f2fec77e60b6ee193dd7688168471d1846",
+ "sha256:48e84252a16234ee860206a738f9a5084f830a5d0a1370a418d3af4e917f5e08",
+ "sha256:490a81770599b17b8594227674872dfb215af05f2be0065a32a7c70175fd9e23",
+ "sha256:4a9cd8485a6729387c889c88c24d5eace39dc0c0c9ddb9003f9c81751f654b69",
+ "sha256:4ac52b95a938789fcfcfbf6faf647b45d7be940f0c51f06991c1353db54c67aa",
+ "sha256:4f39485bb02dae23e14cdbad086ab0e468756775bc5c64bb69e7cb756ebc1dcd",
+ "sha256:5133884ec10c9d6bcf7fed4ceb98fb3a6acbb6c2ba1bab6c4b6700d6c39c7595",
+ "sha256:55493b5b6cb6c4ec9a3c310d4ef947dbd34326ee4eecd4249e18e00d84f14be5",
+ "sha256:56d8659c65166641d8f1b5efccc391c62c8a899eff4d528b981cc62b7b402a4b",
+ "sha256:57f405fcb82e2b78df88f04f8aa49ce513ce23bfea073b581597bd52545e9b3f",
+ "sha256:5db80d0b1c8238940b5957dd66b5c818ea40a221f6652fb717c027a562d09c77",
+ "sha256:5dd356a646fe549d42b766cb9075b54eccd3f20604d87a3eff25f1430bba5b2e",
+ "sha256:5e49096c90364317897b8d9fe97e2f86063dc0fc0bcd352017ea6fdd703ff25d",
+ "sha256:5e674cecb61cb160be392da07fd8a71509ef927f437fbf3215432692ed385151",
+ "sha256:5fecba510f6b8f9cf1dfd103d785a61da47221cf09d4cec10946d1950daf1a17",
+ "sha256:610371fe95c7e8e824ad8762248836cabbb5a4ab8befb982cb7dc8df773075d3",
+ "sha256:61a9da95a836e7d300945891265bbeb6510a860863f6b01ed6397e6239a31253",
+ "sha256:635482cd183a1856da75a39c473a2e222697b7927f1955f586db83fc8a5da17c",
+ "sha256:63fa79eab192623acb169de1dfe8e733598c4047d06f7712347d1bb810a5ad20",
+ "sha256:647983d2609be6155c748249e770ab7e75e15e386cbf15469569f3eaf165bbb7",
+ "sha256:665207e41bacd435db001099eeab44103197c2c1a729d73ade74688a905ed4ce",
+ "sha256:690ab9d06cd689b79aab84afa0984a1bc85ea4b93b0a42b015f3ff5e2f5b08be",
+ "sha256:6b5dc780377e35be6b0cf6fec7fb4a45cadb1e834bc0ef2ce596cc015290ef69",
+ "sha256:6c0f60b05cc708e6cdf68dbca86b7a36d99695962db0189bb1b3884ca3b28e90",
+ "sha256:6c997a1703401089bc02d731e360127428fb4ecdf6524268e8975882ff02528b",
+ "sha256:6d416ed4058ea38f69884f3db523b0414c0094d262dde63694aa254e32a433e6",
+ "sha256:6f198b622a3ccf02eeab00de71f6b2f45b1b50b1979aa56b25178c963e475950",
+ "sha256:6f64c66b3b13758b4f8f56f17972cd0ce5d0033d19d7332ed32e2dbdbce94dec",
+ "sha256:6fe39780de6916ecb1c664eda81802e6310fd9d4a07dccb13a86b63918e00e65",
+ "sha256:6ff5a98456194621f757d6226f88035c00ebbeab0a9095dc9b5a9ec5cc94b50b",
+ "sha256:70082b2a8f099b8bf660b553b22a4b8ffd34fcc09ef154fc6b9c7108518fc124",
+ "sha256:72df83ee0eb89b070e28d1260786d13485b98a8b80228c7439d303dd9aac9970",
+ "sha256:7a7ddc9a8ca1795166a1ca80364b8ce74187fc210e112d3fb048b711b934f36c",
+ "sha256:7ab7ad8b557d1f21e8b4a97a2c1a2ac82cf10c92d6a875c90f038ca9ed85dc5b",
+ "sha256:7bb75921a4885d30d9e881d7a595ce50407a8a82933e15451ad7e0d89d1a5944",
+ "sha256:7d2eed2e4d231278a2ccab3f4bfa2c1e39855f336475f7756a281d767d2b1753",
+ "sha256:82c27460ff2ea683159a204db2f9ce0f9549dec7070aa0809fbc36226bac1816",
+ "sha256:837c1859913798d8bebcd98d4a037e113f8d79e81733009bf590e449769eecb3",
+ "sha256:862c29f7e7927e71391df6700079b1dd7c2aeb75115dc46e37004a092f8f16b7",
+ "sha256:8c8a63d55cdf3c716225a2f8741a1df7a52b5fa98ac7530165c9cc9b32feabcc",
+ "sha256:90aa2a7f9cc1cd2e8082db61618a2ed0f4197eef52266a6420e88277f184e328",
+ "sha256:92e06a5ecabd6e8352da58b9201ae241a1e7382c1419defa9d2f64ad4ea6abf2",
+ "sha256:98620c9c4c22568ad70f57b80527c780b6f8fd26e36507bf8e2273262a228275",
+ "sha256:9887e9455398a1517294dec14e23ed9a178c8dd909f2788e971b66623d3f7c16",
+ "sha256:99034ec353c973e2c89555866083491b9a2dbe81f2fbe15fb0f2b68506232f01",
+ "sha256:994fa00a9b0d14c6e6926ff5ade98d4d83676a5eeab6f87718170e481396d380",
+ "sha256:99896cc18fb421be93e337d6bf2c1686ba330bc2d5c0ef581c842f0639a5e886",
+ "sha256:9c407afee6ea4caa313e815814e76d8447fcd32f81e3331d7937c2d33ab80c1b",
+ "sha256:9ce34fc5a6f6c9b2ba4a8060009d989e4f55630e3f9c3bf5962f42dcc31355ef",
+ "sha256:9e1c602c55dc8ec05cc7e0a8e31f3ad3d4644dbcb7ac39114105c9bd0384371f",
+ "sha256:9e61478a8a06e6456ff2e66b9ac18f7f28d76a75fa5fda0c5198a4de07b8dcb8",
+ "sha256:a87ab35e92d40b53c5373af36625794e422f83ec56122f0a63871892856d721c",
+ "sha256:aabd43208e335f4c3f0b56de3464b066dd425983a58f6eeb5738bcd7465403db",
+ "sha256:ad2027883344e70ddddff02259411f80faf47d5e779eb0e39cb95ee546fa628c",
+ "sha256:ae9f7055e7357b2873866a3d77d0136a251ca2ae2dde3d8cdb819425d717c177",
+ "sha256:af06c9099df15ee44fda3fdfa002bfe37de02901c2b3a5ef350853861ef3b4b5",
+ "sha256:b5f85bffdfe17da7dfff78eb32b261c27f3cd64c060033645079493f4cebc8e9",
+ "sha256:b7b7e6be82fd6d5256adabb82253c5c307de981cc20c0ce4cff0cbe6de88529b",
+ "sha256:b9190d4901d7786af9ab0ec46172e27cf7d72cdba2b82ee38eb40aadd3239a6e",
+ "sha256:bb2d4ad7f9bac398a7a19f07bd09fd5b2c1e4eeab316065aa84a305f62fc5361",
+ "sha256:bda6af6fb4d5fe9532620e4f72d3c7efcdf7472ead9037b184c0a060f0e7c71f",
+ "sha256:c07eaf30bc072b179acc8ac50519a2a81f03f88a220750c6d83dadbdc33fb1de",
+ "sha256:c0ea77435b1d5a27cccf27f8762f50e73fd2d94e8e412a8e5cdedb650b36d5fc",
+ "sha256:c1fa3f84cee5211a3e574ba76ac9596df8c8d16a855f31a25681d23eadcc6c16",
+ "sha256:c90f34f1b1905d7d6c42b25b6ffb4e5066e6c95c7715169e3332b1760614d092",
+ "sha256:cb6374a84f11a6b25e63aa69ee2d015286048c1efee93c4a3b5b8df62da79ff8",
+ "sha256:cc5d0f82cf05beb6c0d463398173a09357ed4f4a631f7641cef6f6480a05bc57",
+ "sha256:cce8e5243d95068f595155663e3e18b1938b16cf716dce6cf2491ebc08b98d96",
+ "sha256:cda393bb35828e3993fcaf39c8ede78b16614954eb15fe77336d5d141be40f76",
+ "sha256:ce6505846d29f860966e0e9cfadaad1041a7d8ce7dc4af39940fcb1877dec29a",
+ "sha256:d10a442c6450ebd35aa89392b8e4b0459ba474df63fc5e6828573d0a31a627ec",
+ "sha256:d1d52739de118acf82bfbaf7046955ead4fb613d24ba4187be565cd91ff9a64e",
+ "sha256:d36e9097a1bc6eebe8858216ccd2326f561662de4e50c27533452491c1cf1685",
+ "sha256:d514026ca1c473cc14440e4d7bdf6721c642455b647a74c8143c0f22da358c28",
+ "sha256:d539a51be176e874ed66029b2df8cb8e1123a3e6420d52a690de6effded4f20d",
+ "sha256:d7b3a8a4bbd83ad8b29758f5d24bab10a3f2de87970db36f1e3651c733353136",
+ "sha256:d963442186918577ad83e3a8c5564eeeba90e2da233ce59f6eeccbb7b5cf771e",
+ "sha256:db6538d733047f9ce4b74ee29c77643a1f99e4ca36e273495da93fbeedd2f03f",
+ "sha256:e27003c0a93a5aa541c260bd8ba8b917a2af4c378eb684daa9f1565f5e363181",
+ "sha256:e3037d02425863ce9501afbaa04ba967162810004bacde39a53ea9a5b740eb32",
+ "sha256:e337dceb936f333775cf51d49f6badb8cd3d2a6b27e8cb443a6c5861fbf3c1f9",
+ "sha256:e33dfc13c02d9c4e55bcf3f3b2eb448537823a6f6f30bf737b2974b63a530bc9",
+ "sha256:e7663a6803a47255c32cc7e17ae3ccfe02dba5b0849f87729651c0263a129d20",
+ "sha256:ecc27adda0d1e1bc39793b41fdd562d2f4bc4dcee6ee0e3c733d519c332183b2",
+ "sha256:edfd2b0cad175780f8668fd6f66486354b8770e4057e44038daaa4a066f8ddff",
+ "sha256:f02091b425bbcc2d8481913855c744baa4dd73e334814337b201d837e9040ef7",
+ "sha256:f4381151a29a7b9307842ff444609c4b9a402775fbe9afb3ec3e34ec0396bbae",
+ "sha256:f568bdc17b7ebb3a323ee8920468d2ed74af84da911a00d9585ac887bac73b88",
+ "sha256:f584dd93ef6ddb10ba028e247fe1fc0ba52ed70ca4d596bb8d52bf4304c147ec",
+ "sha256:f5e8a0ce681ddabf6a35d7b817d74a94ab237ae7233a5b97118db0b4e524473f",
+ "sha256:f93c60d8c522b4ecea35dd6c58cc42f251ecb12882a5d67d4bd8d12137fbd05b",
+ "sha256:fbaf76379bf2a72e534bbb1276d45e63a80d8cade17953ed79a350d6426262fb"
],
"markers": "python_version >= '3.10'",
- "version": "==2026.7.19"
+ "version": "==2026.9.3"
},
"replicate": {
"hashes": [
@@ -2791,104 +2878,68 @@
},
"resvg-py": {
"hashes": [
- "sha256:03efcaa6929b6ed5abcfb4984399f3a3c35478ba96965b7f9757c642b22dbe70",
- "sha256:17856adeefcf221c632c694f80d2648d207b0f73654caa00218d48955c3b0622",
- "sha256:18524420b5a96336a147703875d2d6a74b974776f7a3081ca416c7413b90c45b",
- "sha256:1a858234af1c42ff9f75d449b0b2f20ac819c1075bb9221c2c3170840cdb5685",
- "sha256:1d1d615d1f39371ca1e6f43c2799ae7de9d8dfafd18fd78b7d04b100f987f9ec",
- "sha256:20abc5a85e47b859613806e4feacc62409b513fb9d00831c4c24a449beee8840",
- "sha256:213f37b2d6f79f7fb0c4311b75483c29c027ce31c7edf545965aea0e57da7497",
- "sha256:2524fc51eb2d4cb39d5850864416d3fca152979be2490a388083e71f45ad3370",
- "sha256:28c401eba4e7b43b51ae5c16aa0b684f92dade7b8ca7be0e5141b81e0bfed356",
- "sha256:28fc4e4e53bce5999f496b8491fe3f637f969792a0a1da48fe489f943cf0101f",
- "sha256:293e532a31a99231024290949249592bcf3823c75b23985684265f98281537ba",
- "sha256:2ab7fc40a6c6a2f050c79ac2e3568a98e30f1650ea5513893ed6615f73355fda",
- "sha256:2d1a8b87618e1dcaf86f21d71d6b5573a7bbf666509df47603aa71abcb0b7c85",
- "sha256:2f3a199a51221fee9bd904c3a0b5c60c842e0197b21f0e3198d3de063a7a284a",
- "sha256:3246c1dc7fbf5ed9a71cc730238d2089efeb9ea7c3e774e040d0ec55c5571a96",
- "sha256:341a5dd87fc2089e1d6b227d6e0730f7686192ec2fd9d7543a897b0ccfef3674",
- "sha256:34485a62e3dc1e502d91855154d57cb54e6dcaf28a558e33205d77892585360d",
- "sha256:345e6e65b6f1d3b55c088e0aff1ca6ad40b85c43140811c444731bb0dfe7433d",
- "sha256:36b12285554208096e31ea55d4135cd0f4a6141761e52b8e65252bb3eda8e555",
- "sha256:37df4823b84e9b3248c17714df8f1c35420a5f5f6e04f22045ca9c09e8c94628",
- "sha256:3936060b7050b9174087756c7db3317258759770cf7a68aa8c79f34e6639f66d",
- "sha256:397ed997a2f7a737f5d78fe49ac41f45b7f2481e7001b3691a7a12f71267ed88",
- "sha256:3d48bdfd3a75ef7de6d27fd0b50a43bd8dd7a66fa93e07f690a98a9767478bb2",
- "sha256:431ebf3a667f1b02c4c6ae53d212969cc0e1c551ff1bffe29adeac6d7a9f9fb7",
- "sha256:45427ef75e949f01a419d6f2a71ff37f14110d52a7364fe43f7248b88fd282da",
- "sha256:47b2de290d80891742b800eec09076f4c299f468181cb6c81656ffd10c4d8267",
- "sha256:4ae0760883318833f6d66d7b9702222c94167d5db60a7e3018298cb4d5fa0f26",
- "sha256:4b30970735648dcc06327a8d614dce8f77a4c307d023d2c026e19bca3996325c",
- "sha256:4fffccdb51359a867ca4aa602c15cda05ea9df63bf6d7860beb4b527850e615e",
- "sha256:55fa6c84e675c95b0119e5d24af0f94b08b90942e532494290253f27df6b77f4",
- "sha256:57194e058aacee5e5d7694c5dbb00cde56d9a760ef1433a7e7f2a8b22e2c7576",
- "sha256:5722df8ac30b86fbd72e0ec4c63bf17bb16067ee006c232f93c37f42a846308e",
- "sha256:5a14768012cf41232dd78ef8e571cf942f69ac8d3dd06923c408f9856f431c5a",
- "sha256:5aaed9b203336f9f887b6724351f1c41898e91fdf5a02b1d427667da019b533a",
- "sha256:5b533eb8b79e5ee36d59004397f78568f893da288e4f40e7eba907a4f8668cf4",
- "sha256:5e6f8f78f40b7393960e4dddd937304878aa84d6f90effaf609e6308fde34731",
- "sha256:626353e9bb20d26f8032d080a4efe3683bfdff739b38be574167077d552206f8",
- "sha256:6515cb342b3cdac57c3c70ec4bdb05b8f26f10ab9a90d208ccdfd731a38749cd",
- "sha256:66cf04a5708a6d31329aca4820079b8aaab56389e53adfff6f38ce33a54e11ce",
- "sha256:6b92a38614550852b522ce20523efad16b9e7b2dad31ec61eb7f04897c0ee05c",
- "sha256:6e6f3180cae3505706ce32eec1233a27fc7b9d5d304e20c04f79d07897c6a5f7",
- "sha256:6ea28f39c363c89530c2aad01dc9eca94cfabc6bf1f899341921316aa506518c",
- "sha256:7884f3e2d85e943fc9939ba6a639e373f45043ffec3b735df1fe9f02cb8eac91",
- "sha256:78e9a1a5614f57f8f69a9c405a49eebeac9d81f008d807d33b09f9d67f52e664",
- "sha256:79c1922c3db974f71cc8ee23bd6b18fdb24e13c74493cb1c9428c9f126f89ff8",
- "sha256:81ad1f27feacab23317a4d639345e9c58dd2bbe8665b5010d52d774217dcf944",
- "sha256:83e9d1a01152f3a2a2266486e3ff04b37b6d535902a8923d2fbe95b8b201ce76",
- "sha256:847c32934bad0726265fd07979244e909da6c418a9ee02542ceb75d88fc2941d",
- "sha256:864de609f4866580e033541273c7b61cb05ab815db7e52c089cea3c5aea60366",
- "sha256:8c7bec5f079bf2debe16af59940fd01749f5d17700d15dd66cb4f4442cb80a44",
- "sha256:903e098faad9862ec94fdbac019574cb2cc72381f1529f5aede133d7c954e695",
- "sha256:9171465e12a1c8bae5f8d2e4929862b542d11d02d0db6e453ccd96c9e4d7edb1",
- "sha256:9268ceb1629d34cfc6f244f0ad7a8fcd6376f9f4ce822c0c3c30df79de712fd7",
- "sha256:a1a62745f93e21b2c46ad595d7dd70d36560506a1963d1829853cce4ca2f15ba",
- "sha256:a4f185e73898995adceabc59a81c0da4765176d751b90be3aa73203990b36d28",
- "sha256:aaac963aa895ee7a23e41209e6625605771f3807bee9a3556ea346517754cd0e",
- "sha256:abe83cbb03fb990f9ad0af3e6a7981dd82d4175c5e64a41ebe6d520e0b5bd033",
- "sha256:ad4ca89b858b8a4955f675d8c053308a92a35071e45881d0dec925073cb064a5",
- "sha256:af10034ce6e6d164e4ad7b6aac50d62ec0a231cedff7d098e53e110e3d637617",
- "sha256:afd3008c6ec8af1cea3130826d6a58cbb1c2c97039e501e287bcd7243de5a28a",
- "sha256:b3f81dd5ffd49db66a0229b5ef3b82a63b0d2fbc3812fa0123ce1cdf0173fc5f",
- "sha256:b4c78673fa0fd242dd7d9d802f475bdefb3cb57e5f504c2e2f3adca4c305d3ef",
- "sha256:b5dc3f2f4550bd0ca3e03dd16d3a3af44a08b116a51012980eb4becd132e286d",
- "sha256:b86beb3738eb7426169bb6f23b3aad060eb7939fbce8e27540a138c82dce281d",
- "sha256:b8bed4f81f9d9e09a1c7f15d1f51c2797dea80722147c6c907ab56532f1593aa",
- "sha256:ba8e52c504b220db092926c5c312a8233d95864bf0a5a3674be936f92a70c913",
- "sha256:bd3d3c86ca7ab2ec809bd9e617ca59c6c37778464cebd77a542851c30d9dd018",
- "sha256:bf5884dfc349510b874a107ee2eaec2ae6e7f6627bb6f754f9c3bb1b09a1bb11",
- "sha256:c1ed821036d7e9009fb11b63ae7f41d158fe0a66ebd64ab6825fd3830ff1c2c2",
- "sha256:c76c856fcdd1c15bd0134ae75d563d20f4d50ca961485c02f7f8aceb1d8aa385",
- "sha256:c88a143eaf64316bdff1fafb93e622b276d3588d1d59ea4f5f03108e4d399dca",
- "sha256:ca8a487f375d585d4dca7afedbfc17667731248b53d924d6471e73d42fc4f373",
- "sha256:cb2dfec2e604477a2e831cb3d505b541590c1261df8a1e6d02745f80f405047c",
- "sha256:cbe903a3be8538c06dedd7e992da0a71221a9ce938a7a6048731d2b152ee210d",
- "sha256:d1f95e2e6cbb261d50d6e00fe731b6e24a271d827ef39f1f8987ec2352e077d2",
- "sha256:d2b747d6f77fc985f7b7c5798b69ddbf0df8303d01b87899e34a2eba76e75e86",
- "sha256:d3c6a565b368d6b6440ede6a8ed2ebab1f219586c09298cd27f50745fdef8e44",
- "sha256:d431e352f0ebf006946725f86f3c7e7598b8401e83138ab1f21fe225f4a1cbb5",
- "sha256:d6d38e48aedf6738097f8e59f4de83fcc48a0a783ff1457a3f614525944dd4e7",
- "sha256:d95c89e55bef7093c9894a08a0f16482c4cc35ff023b5b053075675841e5c178",
- "sha256:dbf698e09f2671a6d0f5757dbc15e5cb675b003ab5f11147e097553e8f2a1616",
- "sha256:dd5b113ce8c8daf2ce37b547c6e1c359f8e9485ca1affa4cb4d6d482c0becede",
- "sha256:dd7f32e0e3b3a7d5d8fc22384859303161e188eee614558b322f001848db2c73",
- "sha256:de8191176a578492a6922caf33599a297322c78173e1b939b9464459fed635c7",
- "sha256:e148630bea35d7897bc66116fb440b6ac826da10fad088092104e56343c12088",
- "sha256:e4e6766406b8ca500c1f5c17d429aa3394d441d0381bdcf9777a86fb2a29df54",
- "sha256:e5092efbad8e85104d2b014e9608ad3a5991e552acf57ff6339386af29831ac4",
- "sha256:e6850ea01a74ba8ef922db312bc06393d46fff1411f60d1d6c7d02586914744e",
- "sha256:f0dc7bb865abb7a3cb6092d63b9d0e06216c4b2d1615f5b9b4b488c120ed2326",
- "sha256:f5bc650ce1fed0db08a719036d5b3e966c454921eb5ffdca19d48594e74ceabc",
- "sha256:fa413cfd0854f7449cd37748ce642ce491d2e6e23f369d180b2861ed5d0e2ddf",
- "sha256:fc0fd718a2db0b46a53d87537a7e3d9160e3074ad3a8b752e6d1b9a0af6edcb3",
- "sha256:fe4ab3bd1e5e3ca3935d77f3fd1f681f5f7c555226cbc79a5e0153fea701f30e",
- "sha256:fef023a7806b1749fb5ecbe27c4250ba7399f14721644b33cf0d3f5971c870b0"
+ "sha256:011111a4c3f46d409e989fe88ec783a3ffae1f3877092ab0351aaf2167fe399d",
+ "sha256:04b32b1e2d7a848124d9b96bc7446ceae71ea144d950007e93c4a382f7ee134c",
+ "sha256:0fca2d6b28938e7fa7553a7f9c5f330b908c7fd8c50f4fe3d175ddcc5e958879",
+ "sha256:12cdd0349ebd8efaade78f74fc3fc08fcdd3f21f7152fb199a561176a64581bf",
+ "sha256:1447c10535c4fa122bb20f702da5d23ad5053cdff831aa2d6f6b482ebab12485",
+ "sha256:17640c3bb2f4498a6aa61d256ec21257b32e68fd2564b509c4919f5171561b99",
+ "sha256:1abaadac95daa2907e3fa0f668c90a099d4bfffe0a6fa7cf36d30c607bfd5797",
+ "sha256:1e66216f78c84a27d34ce75f4535d8e26565771be4f1848ddc71e8a7ce78973a",
+ "sha256:1f6b8956c4143dbfe107bcd35799d0dfd778a40a8cd537893c0bf489898a6c3c",
+ "sha256:1f91870d5315168093d546777fccece4406ad2053c1908f5ceab3c39f2c49e7c",
+ "sha256:200baa4a01b6779d7b6f3fa31e5eabfc5ab594a1317d75853ee73c5229686599",
+ "sha256:2715f2b88ce2cf91f57ff37bb34c5909c8007de431d488e9c3ebf6cf2d69c91b",
+ "sha256:326358d1a83fba3c2c373576f15ad2b4f5bc6e90aad167682e39e3e15ea72c31",
+ "sha256:3651dfe44c05bf3c594d2f074af06ba49a1adb0c11ed2e62f0a0ae5647d4e689",
+ "sha256:403fed36186bbe4ef4eb3ec1d5fabe003a1c90ad71f145ca0e9b9f7f80e70696",
+ "sha256:45b2e66f76e7649155dc768c3cd1f5a94907d0086c2bde22da14c9ecbf9eda9a",
+ "sha256:4a5db1a607059a48f5d4c20c7363e4888e001b11a04465d36e3ca77a511651eb",
+ "sha256:4bd8da85e5332aded549894d7fe4aec6f19a681ecda3385acbfdf1a1c67bc8da",
+ "sha256:4beec3f2a6c59b8c3f2fa45dd167392c8074a326debad9277cb305a514546be8",
+ "sha256:51fa0564ad1a3e82307c1aed7222b66edeb3b8c595251709f929b0a819109da1",
+ "sha256:5547fc79ee600ee0e40ad01cdeb36140a74e85cfad2722973dae654db3667fcd",
+ "sha256:55d65708e2dee0de77cccc0d03d21cd148a491c2bc6ef25542081db8eee74923",
+ "sha256:5befa08450f4248b9670e054f446065d0fc33c1a4ff302baaacc205ddee97b3c",
+ "sha256:5c026b67b79604f32865e132ef68ff25633b8668e35b29ad412fcef906c0c39a",
+ "sha256:66e5a7699f2b00024ed7e95ec53df05bb3da277ee5bc86f867d487e310e4d392",
+ "sha256:6d3bf8e866b4e129524d9432a809138b2d100931d8d635bc81294002abcdfd46",
+ "sha256:75e6822b492c66d85f03a6f2510ac69902ff0509a2a86cd50ef30ebb73c714ea",
+ "sha256:7b43f942157f5d16126e108dab8ab37e4bc2b198099e5f6274b753a3b1ac7b6e",
+ "sha256:7c3e8c2324fc2bcf03c1010b7987adf5ff4ce43e8fc1a7c9b8271cbbcca6ba37",
+ "sha256:7cf22e9feaa41ac4eb781ec266b685d001bd02dccd9c28b74ca9ed2cc755891c",
+ "sha256:7fc91829a4d12d80071e9f4f191a9f459adf310919cbdf1377711b30dfa996b5",
+ "sha256:8016e2006c09953570af466e7674c398c1f255cb00022152b15e18f9e8ca3af8",
+ "sha256:84f2378ecc7a8e38b03429efaefc816daec1b1970114909a6f973393b297c91b",
+ "sha256:869e4ab0b8f4a403d6fac93d2c4e3df79488b9f6fd053ba0f4fa4ed45d456fe5",
+ "sha256:8c4cc14543c29b753db751eace1dadcf0d58d77aa58be5370393bcfbcc1cbc2f",
+ "sha256:92cdc56331224980b85c1604c7da9bef37737657cf123d88e920ca10a07c6a11",
+ "sha256:977921f22b0a3283e6cd121339a2aff51d0df3b542ce7a5f96fa3a87f8d65106",
+ "sha256:9901e2f9ce53e7535d2676123c8d4894bff040f52821e54192605b5dd4fb5af9",
+ "sha256:9d3f5c2544d6b5f74847513e07e6ab6a70f9e7f0d8a141bc16bd4b0c555f4234",
+ "sha256:9da8e52d7d5d16b288aa47fa830fd66301a6b6f135f9f37fa9f4854b7a722e6d",
+ "sha256:9ebcc40941811b49001ad4e721aa87f489b74c0132ff3fcbceed97305c944749",
+ "sha256:a9f5583cf9f3d806ee802b948bf0632acd680661ca4f6e8007eac75ac3f09e1e",
+ "sha256:b0284c50c7e3b009e97e5c64a2d31e02b8bb36cd066d947fda9e43f480ca19f7",
+ "sha256:b8481cdbaf7fea5dbf2bfe57e86201c6a40193c462e365729185c66849b5966a",
+ "sha256:c113a655f558cd1d62616a459ad7ad61072cafdb3c997c9d8f83077a0d186af9",
+ "sha256:c184cad5c3593dbe655ef9f304068fa941646947d94767dbd1afcbf094c8dae5",
+ "sha256:c1ed526890579f8bf1afcf6c17f29c22659196c57b2c760e485f15dfc93dca64",
+ "sha256:c2a493b6ada049cdee60b5ebe81f1eec8d36766c38962dadfd8d0ec8e5201cae",
+ "sha256:c7fad8f8c28e770da8783dc429bfa0d71f2abe740be2f8d726308a669a392919",
+ "sha256:cfe18bc3d36cc885190f450d5f0d26473c5cecb86b28bcb714f7a025e1cfd5c2",
+ "sha256:d54a8c85e7d6f4ba55f39c2330c7830d8c98a7dc205ca3c2ca069f9b11cb01c4",
+ "sha256:d597eef189a8e728c8026417ea51b61720c83d2ed262b508362c4309cd57bc8a",
+ "sha256:e3ae9f72f7a265953c3cec67dbb849d76fe9391658820a2fd05769d858869fdd",
+ "sha256:e9bbb65e6a969fc792b6bcff7d7f203ab58775475cf30db4577a05f62be72904",
+ "sha256:efa682ad33f8d2fa22cc1034e606fed5eee09b516c09a889b01ef07ce80a07fe",
+ "sha256:f0c834262db96eac4d5767e1025c21efefa0ed0359bded8dfd4b79fc7549694f",
+ "sha256:f322d7bf0ddab60156d6cf1883718b726c1c210bd7623e253756986798c5c83e",
+ "sha256:feee1ee6c2c0b64018c046a7604240c233c6cf14496e475238370c7d9a9db455"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==0.3.3"
+ "version": "==0.5.0"
},
"rsa": {
"hashes": [
@@ -2900,28 +2951,28 @@
},
"ruff": {
"hashes": [
- "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f",
- "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe",
- "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318",
- "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140",
- "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8",
- "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1",
- "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344",
- "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700",
- "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f",
- "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa",
- "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b",
- "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690",
- "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c",
- "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f",
- "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f",
- "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd",
- "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0",
- "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12"
+ "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70",
+ "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25",
+ "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39",
+ "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3",
+ "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88",
+ "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d",
+ "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953",
+ "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8",
+ "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e",
+ "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5",
+ "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876",
+ "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c",
+ "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718",
+ "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4",
+ "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757",
+ "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050",
+ "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1",
+ "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==0.16.2"
+ "version": "==0.16.6"
},
"s3transfer": {
"hashes": [
@@ -2949,84 +3000,78 @@
},
"soupsieve": {
"hashes": [
- "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c",
- "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba"
+ "sha256:4a55d8cf158a9c2e587fa4922f1bbb91d68ac829e2d6f25403a85747c71daf74",
+ "sha256:8089a26fd974ca7a1f30276d3d8492ab266ab15af581642dfe8aa162e0c1c823"
],
"markers": "python_version >= '3.10'",
- "version": "==2.9.1"
+ "version": "==2.9.2"
},
"sqlalchemy": {
"hashes": [
- "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23",
- "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1",
- "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8",
- "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72",
- "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0",
- "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5",
- "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e",
- "sha256:111604e637da87031255ddc26c7d7bc22bc6af6f5d459ccff3af1b4660233a85",
- "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d",
- "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2",
- "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba",
- "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652",
- "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f",
- "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9",
- "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84",
- "sha256:247acaa29ccef6250dfd6a3eedf8f94ddf23564180a39fe362e32ae9dbdbde46",
- "sha256:2a97eaad21c84b4ef8010b11eeba9fe6153eb0b3df3ff8b6abc309df1b978ef7",
- "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080",
- "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d",
- "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d",
- "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54",
- "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd",
- "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195",
- "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc",
- "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e",
- "sha256:59cab3686b1bc039dd9cded2f8d0c08a246e84e76bd4ab5b4f18c7cdae293825",
- "sha256:6b588fd681ddf0c196b8df1ea49a8913514894b2b8f945a9511b4b48871f99c8",
- "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522",
- "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491",
- "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400",
- "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a",
- "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07",
- "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7",
- "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a",
- "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9",
- "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7",
- "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499",
- "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5",
- "sha256:a42ad6afcbaaa777241e347aa2e29155993045a0d6b7db74da61053ffe875fe0",
- "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604",
- "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265",
- "sha256:aa18ae738b5170e253ad0bb6c4b0f07585081e8a6e50893e4d911d47b39a0904",
- "sha256:ad30ae663711786303fbcd46a47516302d201ee49a877cb3fac61f672895110a",
- "sha256:b21f0e7efc7a5c509e953784e9d1575ebb8b4318960e7e7d7a93bb803626cf64",
- "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d",
- "sha256:b7f08588854bbb724041d9ae9d980d40040c922382e1d9a2ecb390edc4fd5032",
- "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b",
- "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5",
- "sha256:bb1f5062f98b0b3290e72b707747fdd7e0f22d6956b236ba7ca7f5c9971d2da2",
- "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d",
- "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389",
- "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080",
- "sha256:c95ef01f53233a305a874a44a63fbfb1d81cd79b49de0f8529b3548cde437e37",
- "sha256:ca216e8af5c05e326efc7e28716ac2381a7cf9791749f5ee1849dccdc99c9b00",
- "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86",
- "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260",
- "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de",
- "sha256:fa268106c8987639a17a18514cfe0cd9bf17420ab887e1e1bf486da8836135b1"
+ "sha256:11560064cc4696e772298b6221ede59e646386d9f2a85d549365473b972f7850",
+ "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee",
+ "sha256:1b92a1e23ed40022081217b40d2d1feba4f77064e69ef4f39f68bcbbd148452a",
+ "sha256:2d5e53e36e37129fe0be8b9d08b6e4052c10a963ee6cda56c8c10dcc194b99ca",
+ "sha256:2e15b1d1116a64fc399b8c2694a83f3e792fdc58df28514a81e1dc4f8cf22729",
+ "sha256:2eb3c6a64b1bfe6704777cfd504e7b8ad093a5f3e03ce67663a5e6742f294e43",
+ "sha256:2f5fa2b2aca75d2c7f36db3a8dd04717b6fbfd1a964fb32bdeae16698e475ab3",
+ "sha256:2f9eccf8793c8c3f8dd2dfd11b9e400cb27d1d19370ef732b66017e212107822",
+ "sha256:309cc8ba50fc5d2174189dfcd49cdf7aa711f8346afcff19f2642ae4fc449c14",
+ "sha256:37a4d548327b6cab9c7d8cdb4e0e82feabee0110c4d150059068e2d1cfbd99ee",
+ "sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89",
+ "sha256:3c95c3044edddb65e4a2f7194ec52ca5a9736f72d33ca3a6fa4196aedcc689fd",
+ "sha256:410d52be41d17f1a236d19520fbe776257dc16516ed06bd16d433311842aefd9",
+ "sha256:4699dbb8d396d199e7e78fd4d525e3ad3d6008a9c8c0160b87e74c606c2c3736",
+ "sha256:46f0c46f0d360d727b84660b26c62b295d82306ec2c82b701e97747d2c6dcbe1",
+ "sha256:49565daf5af554f538e23aef1fc81a95a4e49658f152285e45c02f5fc44f04cd",
+ "sha256:4b89e93bb89eabdbea9d5d3fa2d6cc6544e733c33064339f91e5292480cf130e",
+ "sha256:50bff43b632a56fbf5ed9afdd76307e1512b62051bcd5afb341ae67205bbb6c8",
+ "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97",
+ "sha256:5f8438a98d49424acf69d0d53c0a522951dfe49a6f2d86417fbb37ad3066ab43",
+ "sha256:651d6d8782e80679e6151707c7b490834d46ada526328895abf567f25e63d29c",
+ "sha256:6c1b7ed45bf87b214e0a9def9c2313949067efe6269db5ef18d542ee13250af7",
+ "sha256:765f439da5bc8696973bc0c8a31fae0912ac3ff1cb9d66246a6b2728ee4fbbc8",
+ "sha256:77a247d3fd179f6583171e7e0e98f40dc6642ed4f655557515a5a7e25923e9a4",
+ "sha256:7a0d48c4b80717c61385b4e966e087c839a66cfd7b780641dcb428f4dba65608",
+ "sha256:812bae5138bfc0aa46fb0686da0fc7f581f68e2bbb05bc24c3713bebaedd1437",
+ "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b",
+ "sha256:8cf993f065bc04caa5000b339e8d9d6f3d9d00251511f850147c516c9e07115f",
+ "sha256:923bb183c1dc64fdf7b717965e3d59938ec4f8b8710b419a21ce403e5da9a9e1",
+ "sha256:9255ceb65a80c1b001129060b63ee776a2e9c288be3b662be36dfbb888fffdcd",
+ "sha256:938325a5373267afc53bfbe72983b20fbd64ca47842aac62433c3da1137ecff1",
+ "sha256:9876b09b9f1ce7398b0ffece585c0a911244c53191187341f6bcae640e133751",
+ "sha256:a593db51b3bae75db17a5738ad5f992244b3a03863f83c28117ee482c6a3f76d",
+ "sha256:a7438774e1091192fc50a2bd8ceff5c596912d00ecd46587e88effdea7826101",
+ "sha256:ab66fa9618269390d4dfa222f2f2f88f7bc4bf5da13905131b818217db7e8057",
+ "sha256:ab9da41e61b9979b910499d633b241df20c51ee5037e5405b11c2faac3cbe1a2",
+ "sha256:afda3ec521d0517d0de783fc70030775841900896d832de5bbd066549290470e",
+ "sha256:b08cddb8989775e3c88799d86704bdfc3ee6e9846118201aa5997f16f27e3a15",
+ "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727",
+ "sha256:c1e61d08bdf4ee2f41024569e3400de7d6734ba498144766b11260936ccfa582",
+ "sha256:c63bda077685c85ca513286547a531ba57e7a68cf0a7ed3bafcc2bbd18896f4d",
+ "sha256:cce4922535db73f9dbb91e3db2b3e851ac629467fd1ebd8e354a60e369521c63",
+ "sha256:cd9206024b8602e7518bbaf44016c29e0045722f09328d8e654941023920d0b3",
+ "sha256:cef328349452ae152637df4d11ce5a0919ecdf0a363e16c830c3518ee33bde72",
+ "sha256:de89de5b5798cafdd7ef7b7b804acec246d6152922128fd9d156cd1701271aff",
+ "sha256:df8f213ceb485d8227b74935eb87ba0d80169a8401eba7835da6e30d6727dac4",
+ "sha256:dfe9ce533dbe4d0a2ae1486546619bd30b76bcd670539a44d910361376175f5e",
+ "sha256:e0c3ce43907374889f3352bdcc6195c970148a2cb71574cd0237a5071a37fb6c",
+ "sha256:e49f51a5d59857a7a0dcaf9469febf7197d9394bd88f00d69c2c4e848112cdbf",
+ "sha256:f1c850792a3b25a3ad74dade3f05e4f402cdebfea27438bcadafaa1617f77bcc",
+ "sha256:f2b09029ef6f260409eefa5dc2b8276f6c3d7b892bfb50d50e8f852257d4a6b4",
+ "sha256:f4d4f7afc682961dc567db70e00a7b5bd81ccd3743c46199b0257f0744902dde"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==2.0.51"
+ "version": "==2.0.52"
},
"starlette": {
"hashes": [
- "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0",
- "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6"
+ "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c",
+ "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b"
],
"markers": "python_version >= '3.10'",
- "version": "==1.3.1"
+ "version": "==1.6.0"
},
"tenacity": {
"hashes": [
@@ -3038,74 +3083,81 @@
},
"tiktoken": {
"hashes": [
- "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4",
- "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58",
- "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2",
- "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f",
- "sha256:2a3b536c55802fe42f4b4644d2be4f04bf788506b48de0a0a658cb58f8bce232",
- "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a",
- "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b",
- "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff",
- "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791",
- "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881",
- "sha256:35e1ea1e0631c04f551297284a1ab7e1f65a3c55a9a48728d5e0f66b4527c04a",
- "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173",
- "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7",
- "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a",
- "sha256:44733b99bfd72b590cd0936b1c01b3b4dd73122db2d544bc1ceeb18a7678c910",
- "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b",
- "sha256:477c9a38e20d0ed248090509acf1e839ad3967a4f00b4b0f958210049f656dee",
- "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4",
- "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad",
- "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b",
- "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448",
- "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce",
- "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24",
- "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424",
- "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed",
- "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154",
- "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf",
- "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e",
- "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7",
- "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec",
- "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67",
- "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615",
- "sha256:7ab10f4a21c2999846940113f6dbd72e0fa06a24119feddd74cc47e85818e06d",
- "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb",
- "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9",
- "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d",
- "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07",
- "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41",
- "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545",
- "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26",
- "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91",
- "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486",
- "sha256:9b842981fa91accdffd48ff6408a977b7a91c3fbda55d353c3c68114d5c9d69e",
- "sha256:9b8858b29804b3a0add25ce9e62fb00f89f621dc754d75d03ca419d17e8ddf67",
- "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649",
- "sha256:a2937ad042d49d50eac6e1ba07c5661d4bd3942a5b1e0c0d08475c4df83676e1",
- "sha256:b8ac2d6420ff05841a89ba5205c6d45f56c4f6843454f3c884b7eb1a2a8dddb2",
- "sha256:b967dfb9d0adf9a631953b1b40717684f04478270fc51bbccdd2f838d67a2f00",
- "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1",
- "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd",
- "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51",
- "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273",
- "sha256:da86f8c96ac1c235d7a3b3eebff1eacfdbcfb8ad792706943268d4d2938fbafe",
- "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2",
- "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471",
- "sha256:ed5a30027cb4d8c7ca8b273d4766f3db3cf58fad9e9f3b1a68a351ffb54873d5",
- "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94"
+ "sha256:087538c080e5ff421abd3a0785ed63c5111d06af98e6cd0d374dbe5969147ca3",
+ "sha256:10f31e63e40313f2e518d87f7086cfa44e45f64cc14d8ae14103b41220c30a14",
+ "sha256:11d8211b290855d2721334ff17dd9b3a17bfb26872be01f25d73612ef7ece890",
+ "sha256:144a3fc369f92b7d548995217c5d6e84038d3572157a0f6f34080d65291d0f78",
+ "sha256:149d97453c4c98c04b081d64a85e635921269b532710d6faf81e9e82b790e7d3",
+ "sha256:14b47e3674f2624803a8acc8fb367b7e24fc53055f9df3296482fe9a3a34a232",
+ "sha256:151d37a150c8f3dfc5f4345597b10e101876bd1bd13494e0185af6b508758d2e",
+ "sha256:18a1b651c4b032004bf7b4f1713391a54b2a341a52c6e8a2b59acae9d16e13c7",
+ "sha256:19d643d701fdaa70e5b9c7f8f96abcaffe77ca5e482a3a1a7dde46feb4284695",
+ "sha256:1b6e4adcfd285c44502aed51df98aaaca4f0fea028165dbf8a9e857b9f98d8ea",
+ "sha256:1f83081065ee5833d35b49e9180f3d8d15622a603dd1c435da0da6cc12b3662f",
+ "sha256:2157f52e4b4d7ac5ecc7457b3716834706e7ef9a46f5144029bfeb7cf71f4e06",
+ "sha256:231dec90efcdccf1b565a1416107736f1e09b1a08fe736ef9d6363e626d03874",
+ "sha256:26cc4b4840fa0e9f4b72ed489883e12f57e00d1021ca794720e3c29a12f0edef",
+ "sha256:26e60f6a956ee171ab728b37b8439905d7ea1db435c30f9822f291e9861c861d",
+ "sha256:2cc19ac87b41c9493c9778ff5847f0c8bbcf5bd0ec6b87ce06c1c802adc8a771",
+ "sha256:2ea70afba6b9eddbf22c165142e5f0a2ad7aa36a452873c48b57bb2aeb8492ae",
+ "sha256:2ec16eb585332c55d022d86354e209ddf27326b1ea3477585ab248e7776d3b1f",
+ "sha256:2fc834fbe3f6a0736905c36ab709537e6840dbd63b982dc9e0216ae7d305ba1a",
+ "sha256:380873f330b741c4435574f37edb20813d04603ace2d53e0a63560e1fec83010",
+ "sha256:3b12e54f8bec91433e41aff65d8d1f209a4f678081163747079806e5361f6c91",
+ "sha256:3c5349c9f916283bba32bec8af69b763e4faa304dc004d0eaaea66a3cf004c1f",
+ "sha256:3de75343041a1c57333b1e707ac8a9769738241d7d6a55d39e12cf84548337c6",
+ "sha256:3fd7c14b1cb45b486c39fc9b3443bb341f3e2fc7e6f31247f3435a5836651632",
+ "sha256:447ada49af4898b5e992f0b5799d2f3af385921102c211947ce3fe960dd919da",
+ "sha256:4d8d91d68353bd167fdf26467e5ff9e56aaa5f87d6410c0238608629e4dc0d33",
+ "sha256:50a7e5646cbac2a8f7c3e8c0934ffda1a4357ee9c44b652434b23c3ed54d0900",
+ "sha256:561e7580f84a79859af1ef6f676968e9030fcc3fe195700b15235bca64f009c9",
+ "sha256:60c47ca69ddda0dea8256fffd12e1b86f4b59734a20e4a70c61f63cc5f021df4",
+ "sha256:6eb94895c45f26bb8f5546e5fd8a069efcf6e3f108ea9d5cbe3bf6f7f3983438",
+ "sha256:728303a072163130c5b477b1f20d6211895569c1d5302c24ffc93a3009160871",
+ "sha256:78571efc311c30b73f31eb949a921d6dac39a5d9dc42d1cfa8f8db157b3447b1",
+ "sha256:7896eea257fe497a2b7134474d909156c6744ce8da35bce88011a960e008aa0d",
+ "sha256:7aab286a020660a039097912a088236b985d18a3090d73f136c4413d29d37ca0",
+ "sha256:7b7acbb7a4b8383707bce22ad3c162006478c27b56368acd3e1fcb1658a80425",
+ "sha256:7db45b98e94adf4173a5cd7422b150999a7ee11ff847783a14f6e1b80cc38cb6",
+ "sha256:86951a971c53979ec857bd8c4a32dc227ab0fd33f6c12a3bd62d3fbf5f0bfcaa",
+ "sha256:86f66c85e796f5d05d5c4a60ec1d40cbfebc47a32464053528c797163fa9ab89",
+ "sha256:8e947aefe98ef74cce94923f90e48c98fe34eb1ec0a6bfdfadfc5a96359bfc36",
+ "sha256:90a762670c7f968184723769a06ed51f5cf5ce5dcd1e30164f25c72d85c2d1f1",
+ "sha256:94f77b60a8ab23580db19ae822744c9716c1720020d2179ca5605112d12326f1",
+ "sha256:979c1524f753b662b0f3cd261b135afe6659cce33caaa7a5ea00dd1756b3055c",
+ "sha256:a140e83317fef02faeeb78d9a8efac623887f2feaf0055c55dcdb2b17f0226ad",
+ "sha256:aa428a559d5fd02ae619aacaace86c7474a1f2702d2c01fc828908dd60f20f7a",
+ "sha256:b950248272f1b303dc32986396e2dccfa10cf6d1e83ec8f0bba1776660305482",
+ "sha256:c2edf09b381fafbc014ae8e018ed25087abb9a3dafa8465a0ea63c6558c47a79",
+ "sha256:c3093001ddce822b4587e6e94bf6de36a5f97b3f31de1c9fc8d4fda144c59ff4",
+ "sha256:c6cb9896a82b9ee44e15ba0b5c8044072f2e4d48acaa704c8d3feeef5ad9487c",
+ "sha256:c77d4a3e1deb2707819df92046b89aad1ac81d27e07616b797cbff3f62c037da",
+ "sha256:ca4db6ff5c5bf600f9b7761a0070ed44dfe5797a76bd432fb978bc480ef40c58",
+ "sha256:cbe2cc3bba939bcdaf103e03df9d5039d33887080b315624be28ec69059e5f94",
+ "sha256:cd8ca1305c1c902fe42c486165f2e4808d9997625c98ffb05b9e0366d99d3948",
+ "sha256:d0781223705199b289faa59601bb9c2441712d4c600dd13c43d8fd6a33d22cd5",
+ "sha256:d6cebe67765569df3dafac8474e4eccf5c19d24140492567a5e58a11445732a4",
+ "sha256:e067f4cbcc5d036e8aff7fe7a6b530a8f4de2e4616ad9005a24a1879e24e6450",
+ "sha256:e2eca764c53490f8930dbce329e0769f11108d87d908282a80c5c130e26e7037",
+ "sha256:e3442bbb2f0c588cec876061e37ae67b455b9df9978b003c8fe30e45f2ef5b42",
+ "sha256:e4ddf863b59347deaa92302dcd90e5eb003cdc9be06ec2b692c38d1bdd9efd49",
+ "sha256:e9c5fe393aab56469f04e432ff851216d3def3436cf5f07e442a240164bf500f",
+ "sha256:eceeff0c62419bc78d4b6e70a4762a4d25df3ae8f2d5946e3853ce93e7a57098",
+ "sha256:f2af4a336ea56d6c14f27741a0e1d8294a35dd0b038bcf990d232ebb54eb994b",
+ "sha256:f3d6cf93fbe2e7117eb7bedca684216fbe328a41f0843ce34245451d8eb2df1c",
+ "sha256:f5e7665f6624e052e5e7f6a36919ab69279decdc976d7b16b4fa15e1897d0513",
+ "sha256:f702e0aeeb6506e57687e881c59e844ebe8f0a6a097ddafe20e3ab25f387be4e"
],
"markers": "python_version >= '3.9'",
- "version": "==0.13.0"
+ "version": "==0.14.0"
},
- "tqdm": {
+ "truststore": {
"hashes": [
- "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b",
- "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622"
+ "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301",
+ "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981"
],
- "markers": "python_version >= '3.8'",
- "version": "==4.69.0"
+ "markers": "python_version >= '3.10'",
+ "version": "==0.10.4"
},
"typing-extensions": {
"hashes": [
@@ -3117,11 +3169,11 @@
},
"typing-inspection": {
"hashes": [
- "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7",
- "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"
+ "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47",
+ "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147"
],
- "markers": "python_version >= '3.9'",
- "version": "==0.4.2"
+ "markers": "python_version >= '3.10'",
+ "version": "==0.4.4"
},
"urllib3": {
"hashes": [
@@ -3234,94 +3286,135 @@
},
"uvicorn": {
"hashes": [
- "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b",
- "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0"
+ "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86",
+ "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==0.51.0"
+ "version": "==0.52.4"
},
"webencodings": {
"hashes": [
- "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
- "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
+ "sha256:565f9ad031c702dae404e27a099e3e09186a3ab1b9520f06d215502b651fd910",
+ "sha256:7fab6269c8bf237c657876b52058ccb182e861518d1c695c1a9aaa8c1c105d5b"
],
- "version": "==0.5.1"
+ "markers": "python_version >= '3.10'",
+ "version": "==0.6.1"
},
"websockets": {
"hashes": [
- "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2",
- "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9",
- "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5",
- "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3",
- "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8",
- "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e",
- "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1",
- "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256",
- "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85",
- "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880",
- "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123",
- "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375",
- "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065",
- "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed",
- "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41",
- "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411",
- "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597",
- "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f",
- "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c",
- "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3",
- "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb",
- "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e",
- "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee",
- "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f",
- "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf",
- "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf",
- "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4",
- "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a",
- "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665",
- "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22",
- "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675",
- "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4",
- "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d",
- "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5",
- "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65",
- "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792",
- "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57",
- "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9",
- "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3",
- "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151",
- "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d",
- "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475",
- "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940",
- "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431",
- "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee",
- "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413",
- "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8",
- "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b",
- "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a",
- "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054",
- "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb",
- "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205",
- "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04",
- "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4",
- "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa",
- "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9",
- "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122",
- "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b",
- "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905",
- "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770",
- "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe",
- "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b",
- "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562",
- "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561",
- "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215",
- "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931",
- "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9",
- "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f",
- "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"
+ "sha256:01fbdcbac298efe19360b94bc0039c8f746f0220ba570f327577bfee81059175",
+ "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a",
+ "sha256:04fd29a0e2fe9414a95b00e92c67ae51bf900c50c0f8a4b2dafdad621f49ea1d",
+ "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985",
+ "sha256:0f62863e8a00a6d33c3d6566ec0b89f23787b747ffe0c3bc71ec0e76b82c94b1",
+ "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573",
+ "sha256:1214e673c404684b9bf7154f5cf43b45025b1a6160fac3a9e438e9c1a97e22cb",
+ "sha256:125f22dbefaf1554fea66fc83851490edb284ce4f501d37ffed2752f418332d9",
+ "sha256:130937b167a52af203c8d58e78d67705874e82759862e3b9671a452fec4abc87",
+ "sha256:1427fb4cf0d72f66333e2cacc3ff5f575bf2d7008166ce991a4a470b21d51a22",
+ "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328",
+ "sha256:1d27fa8462ad6a1cb36206a3d0640b2333340def181fae11ed7f9adeaa5c0747",
+ "sha256:1db4de4a0e95673f7545d393c49eeb0c2f18ac1ef93073218c79d5cdb2ee75ab",
+ "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499",
+ "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d",
+ "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62",
+ "sha256:29dfa8114c4a620c69591c5973860f768eac29d3fd6904f37f34266cb219c512",
+ "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7",
+ "sha256:2a636ff1e7a5c4edf71ef0e79adae7f25dba93b4fcbe3dc958733477ffeb0eaf",
+ "sha256:2bb5d041a8307d2e18782e7ce777f6fdb1e8c2f5d09291484b18c294b789d9aa",
+ "sha256:2e28e602bb13da44fbe518c1781a88e3b9d4c3d48d02c9bad83e546164336f57",
+ "sha256:30bbe120437b5648a77d3519b7024ea09530e0b5b18d3698c5a0ae536fe0cc2e",
+ "sha256:34420aaa64440ebd51ac72ca8a45ef4626429438c9b02e633ae412ed43f925d3",
+ "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0",
+ "sha256:387e8e4aa5df2f90b198fa3cad3478822a89cf905b6a6d6c97dc3664689640cc",
+ "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43",
+ "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe",
+ "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31",
+ "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b",
+ "sha256:42290eb6db4ccaca7012656738214f8514082fb6fa40cdeb61bb9a471b52e383",
+ "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217",
+ "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499",
+ "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8",
+ "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51",
+ "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509",
+ "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d",
+ "sha256:4b57693728576d84ede0a77987ab16881b783d2cd9f1dc180a8fbbc3f79c4428",
+ "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead",
+ "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc",
+ "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1",
+ "sha256:53260c8930da5771cec89439bff99c20c8cb03ddb9588b980697355a83cd4bd3",
+ "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0",
+ "sha256:54509b8e92fee4453e152b7558ddef37ce9705a044922f2095a6105e3f80c96f",
+ "sha256:56cd5fc4f10a9ea8aa0804bddb7b42506cf9e136046f3b4c27de8fec9e2ecba5",
+ "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731",
+ "sha256:5c31aa7e39ee3e8a358573257f1c0bb5c52430d1b637030dd9c8cc2c282926be",
+ "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df",
+ "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb",
+ "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293",
+ "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e",
+ "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7",
+ "sha256:6aaface73b9c71974c6497366d8b9628357f6c9749e09c4ea3610176c63f2ae3",
+ "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3",
+ "sha256:6ff9417c0ada4d0f7d212f928303e5579bdf3ace4c802fa4afabb30995da58c3",
+ "sha256:7421fad442de870a8cbf2287d1cad7e706ece0dbfeba5e911df132cbdc1cb56a",
+ "sha256:7883388947767080f094950b342b30d35a2a06b849cd967c422fa0db72b40ea9",
+ "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56",
+ "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562",
+ "sha256:7dfcad78ea1492ee3a9ec765cb7f51bbc17d477107aaf6b22abf7b2558d1c5a0",
+ "sha256:8087e82f842609734c9b5a1330464f8e94e346ba0e18c832c08bafa4b0d63c15",
+ "sha256:820fb8450edddae3812fd58cbc08e2bf22812cb248ecb5f06dbb82119a56e869",
+ "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7",
+ "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999",
+ "sha256:86d7f0f8bdb25d2c632b72527325e4776430fd5bc61b9118de4e2b8ddb5f5b01",
+ "sha256:8fe0b50da2d84535fb4f7b4bfa951280f97ce3d558a0443b541166d609e67b57",
+ "sha256:90001d893bc368e302ef168d82130b4e4fdd27b85fa094682df9b667c2d48838",
+ "sha256:9246a0d063cfcbcc85f2359dd6876d681213f4790832272aa16641b4ed5d64d4",
+ "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1",
+ "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458",
+ "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3",
+ "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392",
+ "sha256:9c9f23004a3d40e89c01a7955d186a6cc83418d93b749701944ce2de3e95a1f3",
+ "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a",
+ "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9",
+ "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785",
+ "sha256:a6a61aff018180c9c50b7b0da33bfd29d378af3497429c95006c589a23a11648",
+ "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49",
+ "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1",
+ "sha256:b436f6ec4fc3a6b4237c84d3f83170ed2b40bb584222f0ac47a0c8a5921980c7",
+ "sha256:b6b9dadbef0cccd9f4c4ee96b08898afa73e26803bbe0f6aeb5bb12b0074206d",
+ "sha256:b852788aa51764e2d8e4cf5493d559326bcae5e38d16ba25ffa322b034df272a",
+ "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6",
+ "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231",
+ "sha256:cc97814dfb786a83b6e2dc2e79351e1b83e6d715647d6887fcabd83026417a00",
+ "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac",
+ "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea",
+ "sha256:d0fcf657e9f13ff4b177960ab2200237b12994232dfb6df16f1cfe1d4339f93c",
+ "sha256:d14bfb217eb4701e850f1525c9d29d79c44794cdf1c299ead25f39f8c78dea81",
+ "sha256:d57685547e0060cc6fd90ee6a28405d6bd395e525545f13c8d7cd99c78afd79f",
+ "sha256:d6bec75c290fe484a8ba4cacdf838501e17c06ecfbbf31eede81a9e431bd7751",
+ "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68",
+ "sha256:da4ca1a9d72f9030b3146b8d7022719a9f3d478f61efe6f7dd51d243f61c51b2",
+ "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c",
+ "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57",
+ "sha256:dc0fad4933f427acd5b1cec210f3ea6dce7089e1724e4b9ec6ef47c6c04d1b3b",
+ "sha256:dc385593a42e31cd6fb60c19f0ecb015b386603818fc2c6c274fb42bd2bb4165",
+ "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737",
+ "sha256:e047dc87ef7ca50f4d309bf775ad4a71711c58556d75d7bd0604b2317f43e94b",
+ "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854",
+ "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87",
+ "sha256:f0aa4aad3b1b69ad3fd85a0fd0952ec64331c762bd77ec51cc814170873890b2",
+ "sha256:f17dbe07eb3ea7f99e4df9b7e0efefe80fbf30d37a8cc4d561a0aed310bc8847",
+ "sha256:f2769a0344a09e9ccf5b3cce538bc75a51b53eff3275d3896310c8552049195d",
+ "sha256:f55f0b01956a094c8587146d9558c91937e78789c333860ffaf35931a6e5dbc4",
+ "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8",
+ "sha256:f70541f3104339f59f830522d94ebadb1bf47426287381623443d8bb1cdbf33d",
+ "sha256:fb9a0a6dc3d1b3986cb88091b6899f0396651e0f74e2c9766ab8d6ffc3842e29",
+ "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051",
+ "sha256:fd46fff7eb62c24804d234f0051c7a8ea81285ad63e0337d3dcf33ca82aee58a"
],
- "markers": "python_version >= '3.9'",
- "version": "==15.0.1"
+ "markers": "python_version >= '3.10'",
+ "version": "==16.1.1"
},
"wrapt": {
"hashes": [
@@ -3432,205 +3525,232 @@
},
"xai-sdk": {
"hashes": [
- "sha256:88a6a53181fa13d55662e3296dafd2a3cf258803450b7d82c7318b8c64e8a485",
- "sha256:ec695ad8b459a4080c01f6e7bb8abf38ed6a4ec69532bfdd73c4de14db1d485c"
+ "sha256:4af1a629ad9304d0b05fa052d84ce77b7b61242a6d59d5936b692fec95fb52c1",
+ "sha256:65733bfb83826b10872b8c8af58003104cbd33ce69f0fdfc0d52bb670de7b0e7"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==1.17.0"
+ "version": "==1.19.0"
},
"xxhash": {
"hashes": [
- "sha256:000435984a0469b0f822fe76f35bddea0f96a4d6521b3339a60a6428cdee1edc",
- "sha256:00de40f3b42240db23a82a5c682b55d7263d84a26a953240c1aee463409660e3",
- "sha256:01cab782f8a0a05ecad2c63d7ef10f7ab475f660e0d6419d069418c14d88de7c",
- "sha256:0204701e6d01f64254e0e5ff4255812b1febe027ddd7dda63372e27f98b5e91f",
- "sha256:027dee4355f3fcc41481650d846cf6cfc895c85a1ab7acd063063821a0df5b4c",
- "sha256:036a024d8b9c01f70782e09ed98d532e76fd23f950ae7154bd950fe94e90ebec",
- "sha256:0418ec8b2331b9d4d575fc9284427e8e69449d7172e99e1a86fcdd1f51a0a937",
- "sha256:08ea2081f5e88615fec8622a9f87fbe21b8ea58d88cfc02163ca11026ee62a92",
- "sha256:095e1323fa108be1292c54c86da3ef3c7a7dc015b105a52133973bc07a6ad11a",
- "sha256:09a204dd4bb0823daf938cdd0dc8057d5f1e14fe3cbde929424255f23f9de872",
- "sha256:0dfdf19b0d5433a75d61f19dc85737af0f0b95e445c1ad69c855115d05efed45",
- "sha256:0fe37f72a207223d22a4eddc3149d4298993385aa9daef25c039246ca5a309f3",
- "sha256:10e4393ec33633c2f05ad01869e546ad080b1a18f2650503731f153774608b31",
- "sha256:1153265daa10750a9bf8e9b01753d7618024a300925591efaf16b1b7fa536699",
- "sha256:115772daeb71b2f3b9381177017f53e6cf3f3439c840737fdabd21aba6e54920",
- "sha256:12a3cf79dadbab9631230ebc4c51c7c60f1e9cdfb890c15fb733eaafe2e7713c",
- "sha256:12eaeaa9ab8b9e6033a1fa5f6b338aaf55ff4df4bee11b59fd6ee03b19186ee4",
- "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10",
- "sha256:15790b686f8723b845fec6f612a343beb815a25c83117a7fa408d7c8ee5aa8fd",
- "sha256:1731407102b9332cd3c9dadee07db498bc3d437b95d752b5b1a5f7eb730a3738",
- "sha256:1b86ae798a976ccbc1d02af6ccb98f5b4d24756b1f65e995f11d10fe071f486f",
- "sha256:1c332dd48b8cb050da2bb2a3c96d72b1664168650a250ef9718e423df7989e05",
- "sha256:1da930bbcac3e8fbe2191850e2abb57977a99348c12c4b385e1058ac1b0a9ecc",
- "sha256:1f44275ddb0978b67a58a951501903f04d49335a91f7681c9ce122ecb8ccb329",
- "sha256:1ffcc98d8878e449e86dec008cea6f44cfd3a954d2ef24ae7d1cc9f725beec7d",
- "sha256:220d68130f83f7cc86d6edfdeab176adc73d7200bf3a8ec10c629e8cf605c215",
- "sha256:2256e80e4960ee282f63428adb349cb7f8bd8efe4db770d88eb815f4b9860724",
- "sha256:23e710118a5778a45db740b431943a3f2a82a571a052c2768cce6544d9c8c62e",
- "sha256:264710bd335016f303763ce1275c6486df30bb57c2245c91b224c983d7ac39b8",
- "sha256:2666f059a1588a99267e33605365ed89cea92f424b3522806a9f4bd8ad2e3d62",
- "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937",
- "sha256:27cfc2f1ed76f956f36dfe0c56e5f5a3e94cd91eb78b893f63e2ef2ae404fcdf",
- "sha256:2bc7113e6f2b6b3922dd61796ca9f36af09da3773898e7003038dc992fc83b8d",
- "sha256:2e32855b6f9e5b18f449e59d45e3d5778bdeb660632ef2693cca267a11246c75",
- "sha256:2f1c68394818e0595569c2ff3cbc1e6d5a36a434e796f5c526b987b80c8a8c62",
- "sha256:2f8c25a7061d952de589bd0ea0eaadee32378ff83dd6a677b267f9cd86f401f8",
- "sha256:314d05fbc55719ae2438eaaba77bf2508ca4f030b26fa4c9c8c380e81c48fa33",
- "sha256:32a94ad2763e0263d9102037d349002c3d3c401e42770542c3eeb4801f311661",
- "sha256:32ab1e5432690276e71192be7401b55f96db2d0eedea5d44eb1f164505669cc0",
- "sha256:345b07b78e2bf583d71682aa34ae5b5fab575f7a1cb31e10263ebbc6f89f8c42",
- "sha256:3557bec8fcb11738a8920eeb68974bc76b75262f6947998d3147954ce0a4b893",
- "sha256:358650d5bda9c635da699c53adf4e8134af492ecc79c960f917eebf088bb6799",
- "sha256:36fc69160465ae75c6ec4ac9f781bb2aa16ae7ff869e73c26fee85fbb11b9887",
- "sha256:37d5a56c36dcc0b9a87b814cd992598d33863ff683749de6c86081f278d5e629",
- "sha256:38c887aedb696ef8bca19983206d270848558cfae4a91afa6a2fb05dde58ffc5",
- "sha256:39c9d5b61508b0bb68f29e54546de0ed2a74943c6a18585535a7e37356f1dd12",
- "sha256:3a800912a2e5e975d4128969d645c4a2a80aa886ccd6c9b1c6f44529e327e8cf",
- "sha256:3bc0fa90830df1e1277f33cc6e55de9990b83c0319fd8c7412866cfde38b025e",
- "sha256:3c0d84c5f2e086b120bae4e7f551cbda804c1deb10d958478bed4f89ba286dfe",
- "sha256:3c682fcd96eb4bf64be32a4d95f96107e1588005831bd8a741b324fdda01b913",
- "sha256:402db908ea70eaf9800d9182a66596fc86f36655df8f63fdecf7c11da741d86f",
- "sha256:445e0f5a31f2f3546ae0895d4811e159518cdc9d824c11419898d40cfadb677e",
- "sha256:4482380b462ca9e59994d072a877ecadd1cf51102daeeab2db696f96ab763723",
- "sha256:44c89d915a75c11d2547eaee9098fcd80398987c4bff2974a0497a925bf92c07",
- "sha256:454d78e786602278a2a4383d08048482052f4f0c61fa677ca590af08914d9bca",
- "sha256:460261045936975193bfd20549a0de1cd52a33b405cbb972f0d80940c42266cd",
- "sha256:46b39976d008e2a845758650f0ff7136bca004f40da0c8798bd37ac37860154f",
- "sha256:498017fbf2d13a768b3110d084bde39f2bd8664c1de0b8084f8ccc84425b7c88",
- "sha256:49aa8692507835dcc1e8ad8021f20c74c2dc13d83b5112e87877faa2a0035b20",
- "sha256:4b512261801b1e5fde7b6ebf2fef7977339c620cbbca88a0040ad9ad134f4d02",
- "sha256:4bec8b2c909bcfae9a0dc702346007e02a8c9ba5bbde83ffb224aa194f4f9efc",
- "sha256:4d365ee1892c1fa803536f8c6ce21d24b29c9718ec75eb856095c07830f8c478",
- "sha256:4d6e88ddb3c741fbf29e1e7faf429880f8cd1d7aff4303247435a549726b4fb1",
- "sha256:4df57c0b161ec1b3ed0526a67b0db0914b557e86ee8aae51887aec941b261542",
- "sha256:4e0e1b0fb0259c1b75d1251ac0bb4d7ab675d36f7a6bf4ba6aa630dae94f9ffa",
- "sha256:4ef09bbc2519a93cd0f95f2ceb5f7b85919dffea643278e02362bf40e3c4bed1",
- "sha256:5013be3bea7612852c62a7437f3302c1cfb91ca7e703b194459db0b2b2e0d792",
- "sha256:5177aa44eddaa97c6ef0cc00c6d540edb64d51781d2f8fb941612ec61a92c9ed",
- "sha256:51f71a6e2ad071e70c937e41fcb6c19f82c3f9f49831eba850ed4a106ffbb647",
- "sha256:538f5f865df6cd8c32dd63158a0e5b4f5dd08d732a7da8b7228a5a0776c8ce55",
- "sha256:53f3ed9118397074ff63a79b66b7fec1c84c782eecde35c5bc94e420a971c231",
- "sha256:559e3cabe522231909f9de98ef06929edbd53782046bd21aae0c72db6f2a0775",
- "sha256:57189a69c0891e4818853feaa521c972d22c880a001453addea015f48e3c3398",
- "sha256:57f80a898544db78ec6b0be6183bd1bc008933193d4199f5cde36b0e6bd5e062",
- "sha256:58346024d47e84f7d8b3e7f5d6faa1d58acbbe49a8771497872059f58c1d8ea5",
- "sha256:594131ce1aad18db3689781f806db1b065cdaa04f4df36b4c038d2013aefd0bf",
- "sha256:5b96f0024e9840f449bd91b2d005c921a4b666055a0d1b6492463799f32aae22",
- "sha256:5c566b123dce7e4867ca518434cdfb9f84e5023771235b2e3107a26c9a41cbd8",
- "sha256:5d3dfb1f0ff146da7952867a9414f0c7a29762f8825a84879592612fd6139342",
- "sha256:5da703225374e3a4c8d4fd90e26fe7213a52004ec77f88b42b42e9e86d8c6d57",
- "sha256:5db43f249b4be9f99ef4b967863f37094fb40e67effafb78ba4f0356b6396104",
- "sha256:5eed32dad81d6ba8e62dc7b9ffa0500199385d7810a8dd9d4eafaceb8c6e20bb",
- "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147",
- "sha256:614bca2c7cfa87ec95b703e691c3c5eb6c448b6dabbe9776ac53883152951729",
- "sha256:632a34590c090d1285ed5efa5a02be919f3f9a56a64bd25f693fe1e2d27a27fb",
- "sha256:64af54dd1c3a45a27c04942f9a1a4683322bdd127f4745cca4e02549c1d2d2bb",
- "sha256:6536d8677d2fff7e64cd0b98b976df9de7aee0e69590044c2af5f51b76b7a170",
- "sha256:656256c9f9303e47f07d5cb8ae4468285370adfafd7ba48aea33a458e7697626",
- "sha256:6696c8752aded28ff3b16f33ef28ce28fb5d209b80c206746f943199fcf5fd65",
- "sha256:6c7574528bc922f8757f34dd78ed60ab52b1c7973b630f5eae7ba33ec133ce71",
- "sha256:6cee733fe4ccb1737e0997135283c82341e5cfa9cf214b165f9087fb663aaf4f",
- "sha256:6cf633fe83b1d4e6519d7259b33afe40fbba5d3f438730156971dd0cf7730610",
- "sha256:714503083a1f2065c9ad15340dd49ac8a8e948a505a705ffa1750cb951519113",
- "sha256:717b12fdc51819833704e85e6926d76981ffa3f780ef92e33ebb8b26d46bb230",
- "sha256:7258ee276e8772599bc19e14b36f6260306e21b637190cd7cb489a2449d48684",
- "sha256:72eb5ae575cc7ae2b23f6f8064a8b10f638c7149819ae9cc6d20ebd4d37a1629",
- "sha256:7345007c12780985de4fd740148776d1eee18c0d41407c6fa1e48c5450304fe5",
- "sha256:747476436f6891b9773374ce8d48edcc8b12cb5b61b67c6fb6289633747d088f",
- "sha256:77f74e45a1e5574bbbf80181c8027b3a4c65c2248fffbd557bd596fff13102f9",
- "sha256:7801b7223db017b9c0c9ccf37e44524edb35a1544a1c032add22c061c6af0276",
- "sha256:78c794b643d214f1522e7a288bcf5a2de120d26cd170516749a4009dc92722c9",
- "sha256:7b5f97ecfede10d5b2870383620e2d25c8561e217c7bf9081073802b54248d2b",
- "sha256:7dc4bdf008f77c88d544849c48c1a40faf25a5eff6cc466de2e8edc37c191fce",
- "sha256:81f4ed9ca9644bc95cd976bfe10f7a4cafab8ffdc3aed52877d4600e445be7ef",
- "sha256:82c0cedd280eab2e8291270e6c04894dbc096f8159a39dcf1807429f026ca3cc",
- "sha256:8304be0982130954b7fd3aad18e2c6f8ee40254bc3d2e635991c16d77c91e2bd",
- "sha256:83697b0ea1f10e7f5d8b26a4906fa851393c61546c63839643a2b7fe2d868061",
- "sha256:836f11d4474d3228e9909d97216faa4f7505df41cfaf3927eb29809de785a78d",
- "sha256:83b9130b80b216d56fdf9e87131946b353c9627930c061955a101ea82b09fed9",
- "sha256:83d879362ddd0fedd3f2ab8ce7cce3da2049a6d51d16da8af73011c6edf4752f",
- "sha256:848182a391fffdc25605443e832f5b443f25498edeccf9a64343fd84421ca04b",
- "sha256:852bfe059720632e2f16a6a4745e41d20937b2bf2a42a401e2412046bb6971cc",
- "sha256:868a8dcaff1a84ba78038e1cef14fc88ccf84d9b4d12ea604696e0693296aa56",
- "sha256:89b11a5cdd441aa463f6d34ca0241602bc09b001a76994b6059828494108c673",
- "sha256:89df64c10adfe340fb00330042537cdd6bf0d8d78bad73f29cfe5427eed7b084",
- "sha256:8ea8a141eeced4f6262ab6dd71c681ac546a558c30bb586abe087d814b5f85ea",
- "sha256:8f454166c2ffed45636c8d501741e649851ba2f346c4eb73a64c07ac00428f20",
- "sha256:8f759eed402448c2bdbb492e4fba1f20668ffe29688605ea61f0f67f9e4e386d",
- "sha256:9043877a917be88ccf230aa5667c1bd059bce80f4c2727e4defa1b29b7f48b08",
- "sha256:942bc86e9be6fdd6e1175048f5fe8f8fdaaf2309dd1323ef1e155a69cd346780",
- "sha256:947a585bcaa235702b7c59433b485489397f9a163b3f56058b9463a46fd9b74c",
- "sha256:950ac754d16daea42038f38e7465eb84cda4d08d7343c1c915771b29470f065a",
- "sha256:98d8ac1129b4dd39098cffed94d1284aceb61c3aa396757ccc736ac392e4cee5",
- "sha256:98ee81b4b7f3023c9cb04a78cc67610baffcb5812d92f2096cb5a5efc6f19437",
- "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c",
- "sha256:9d45eee3a95a8b61e5b568580caac91f1502ddb731aaf8f4aa448a98660b2fb4",
- "sha256:9db455cb649dcfe4504d6d68a6d83a7315a99a3ca59871dc3ff840671f99adba",
- "sha256:9df56e6df96a60590935e22373041cccc91fd55858763dcffb55bf63b3a2b396",
- "sha256:9e80238259655bf69d7bcd08226a970d7f42605f3157786bfa76dd13472d7fa0",
- "sha256:9f23083e1bd9d901f844af7a126727c486e7eada9a1a6791c8f7e73f94fac656",
- "sha256:a2489d3a776fa380cb8e71f54c7fda268a9baf3de9b1395093fd280f95735907",
- "sha256:a5cd96f6dcdf4fa657b2d95668d71d58455248f98712ecffaa9c528edf40ccae",
- "sha256:a5eed9d41995a83f3332b4e3396abb7f433cac584222bd7e305b606d8353861e",
- "sha256:a6617f30641ba0d8baa1635fbefb1dffc5165ec36d26921bd5cee13497cd937a",
- "sha256:a6e088bd7870775624256a0d84c2a6714afd223b2eeb56b0ca58398e52a32fda",
- "sha256:a98b2f95cab589e0f5e92c48431afb4d56238b8bf6668edcc66166180e9b509b",
- "sha256:ad52a0e4bcc0ba956a953a169d1feec2734a64981d689e4fc8f490f7bf91af60",
- "sha256:af0c9fedc4a2c24e8664953882fe8185f3790b8338c9c700f76f5ad660817711",
- "sha256:afe6380a0e9653a87aa1e6e88fb47718113e5563c7a1cb2bcc23c1d8e17e3961",
- "sha256:affb37f152e55b5e4494bb9d0107f7bb08515c6704fbed82d9f61214d74adc17",
- "sha256:b0093cf7eeb91b84776e8742113afa4bdf47533d36cf719179aaaf1f56f6f8bf",
- "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46",
- "sha256:b30e01a0b97a4bc3f519a4d7a82da3dc53251fb0de5eeea8660dcd4ff094c0c2",
- "sha256:b3ba794c3d885803db6c3116686923f1ec13bc86e621e169a375282b63ea1cc6",
- "sha256:b3e1107fe5ca030f946dfa59fdbb66b5df121c8432f14b0bdd282d17b297f4eb",
- "sha256:b5196cc2574cfec572a5f3fb7cfa5ade27305ae3d06516a082132441aff4c83a",
- "sha256:b6fa3116e40e14e7782fb1a9f872f94b5997de21127c95545ce40196ac1351c5",
- "sha256:bb70573d2995d23932e2871120f78d798ebc3572e54c09e694a18ced95c5f8d9",
- "sha256:bbcdf9c92d21c65bc75426eecea724c8fa0d35a6e201fdf1630011d4cc3aa685",
- "sha256:bcab50a389cc04d87f90092af78a6adba2ab3deca63175a3344ca83514045315",
- "sha256:bf28f55e427e0483acb1f666bd0d869b6d5e5a716680c216ad7befe3d4cfba2e",
- "sha256:bfcd82852c62a60e314670a9602de354c4460f8adad916e2e42a20860c7870bc",
- "sha256:c240939e963653054fc7e4a17c382829cda4aa88a7daf0af841715dbded1b497",
- "sha256:c31a2649bcf1fe97cf11c79848d761df33ac46b3896942d31b640557b486ff6b",
- "sha256:c4ed42965c2cd9081f011be22f69d0e65d3b6165fe7734072fd0c232840bbd4e",
- "sha256:c73b6f652f0745425aa6378319c331293b5341756262e9408ed3d45f183375e6",
- "sha256:c85949d02c85adf6d786eb94858e124989a632a4e65739835b2fc5761827fac3",
- "sha256:c919f38cd3f0b5e8d30b81fd6cac688cf9221560340f0c35cbbb8b2bd77ad6ac",
- "sha256:c959f88160b13b4e730b0d75b459b7929fc0d2225c284c9683ac95d6feeeac6a",
- "sha256:cb3fe820c27593f170770d6c8d791936cf6275d9269405fbb7b30a55363c10c8",
- "sha256:cf399fac542a1c7a4734a435b93df2c55e858c7d31abf6c1bdf46f9ae67fbfd0",
- "sha256:d0b48cdf690a64cedf7258c3dc9506cc41fc86edd7739c40e3098952265dc068",
- "sha256:d247b34bf433c92b41689318fd25d246313cab2275a6a47e2efac178b80d6efe",
- "sha256:d48acabb1e5cb0071009f80d71d7f01b6ba2c1d4b869b1352bb5df3f11bf7dfd",
- "sha256:d5006c65ec507a333479e76e00e2c368781f16c24ededa764763956b32a0e93e",
- "sha256:d58ce8b6cfa9c4d2f230557f69caf7c06369e318015d0b19485095bc2c5963ab",
- "sha256:d59e71153fe9ff85648d00e18649b07e9b22c797291abb7e27274fa06df8b838",
- "sha256:d6a5c0bce213b23b0166fe0d35bcbbe23ce4b968f257cc7eb6fd57cb8e1e6297",
- "sha256:daa86e4b68221d38e669bb236ba112d0335353829fb627c82e5909e4bbe8694c",
- "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670",
- "sha256:dfe0580fbfd5e4af87d0cc52d2044f155d55ebd8c8a93568758a2ea7d8e15975",
- "sha256:e14800b9b10bb39d7a60ad4a310e403164d7b8988a27ae933d4e40618a44088e",
- "sha256:e2a845687219ba3214126f14a8a5861f97c9e065a7d0b8252adb6df13eea86fb",
- "sha256:e3b87cbd974512c0c5fc7b469c36b2cdc9ee6d76e4ec78bccb2c7184611c49b0",
- "sha256:e4a6443968c4e8dc69967e12776776a5952c119cc1bd94168ad1c5ad667c2be1",
- "sha256:e605e0b8abca9457abd5bee737e086ab145a20c25083ef1113013612268872ff",
- "sha256:e6e49370822c1f4d8d90e678b06dbcb08b51a026a7c4b55479e7d467f2e813bc",
- "sha256:e710ad822c493fb80a4fbc1e3d0a807b1422cb90adbe64378f98291b7fa48fef",
- "sha256:ea6a3e734b0fd41b82784a400be946821900daebe610c050a5e0760838a34f99",
- "sha256:ec55d80e9b8a519d742669e0b49e8ce9e6747be42bf3c138158b6543a9c8e489",
- "sha256:ed8bcdab6692fd4ad0dd6241807a24a640a376764460023b8d462d745e6b7b27",
- "sha256:f377012b86c0a23a1df0cf5a1b05aa7187649e472f71c7892e5f2c2815bbe74f",
- "sha256:f6114692261eff4266386cdec0f7d87eee24e317ab397c218b7ae6a76b4c6339",
- "sha256:f8044cf4c77f37968b8c4cbcbf7a0f355d8a437877ae18eba23e3aad953a6cc7",
- "sha256:f8ed8940435834141061da26d27c4dd0d18fb69777bf431f5c6cc46b43349113",
- "sha256:f93e408255ddce525189bf11feaa1be7ee35e55f486c299c97d9caa68d724a5b",
- "sha256:fb9e256a357dfcede7818c6d34e70db2d6b664394803d1de4b6984d2de76c0f1"
+ "sha256:0163b5d259de23ae9e07b7eabf435ce4704f6f205589a2b154e6af4be985ce1b",
+ "sha256:03600a8987849b2bef7be795a60a6052b635c63fa98b718b08ca5ee823691cfc",
+ "sha256:04f9a24de11a6647666d5302fd73d6a5224ce50ddc965fb0bb44cee736e6bd7c",
+ "sha256:06713a5aaf1d0905c5579416c020c02e42b3ceb931e86c7d3b7fb85403dee3f3",
+ "sha256:06d7fbd609503c3be5e65cdb6bb2f040d6a98574404e2e1d5c60815c97fff4aa",
+ "sha256:0718ad66f4ded2411f8e62bdba549ee71e313a2d26ef5060ca3fdbf29897dd3c",
+ "sha256:08ed8da18cd4fd0a6a5d6a444852d8fbd0e565388a74a4937085451b5f1a312a",
+ "sha256:09f9feb118966cc6650e1806205d577eae7ca394aa6acf349a0b62a94bbeb329",
+ "sha256:0ab851b45c70d4992be7cdeeee16f97a0b677408c758c4b1efb1cfe8030bfd37",
+ "sha256:0b1082fd0f089ce9098ed77aad8b777b5d156f8ac601c69cab73811822b8ef07",
+ "sha256:0b20a06454b34f1531fc677c54efe2ecdec691ef9224f7fa919bf2c1363f7ff1",
+ "sha256:0b42a5a26607e4b2409fea174773a66f2dff9dfdbf2c1a851bb7b804e2c97535",
+ "sha256:101aa300de6ceef3d9c77569706330d8921fc45dd82bceed2084f1e9f2557a24",
+ "sha256:1216f7ba5683f17a89eb7dcb4bc50a0b743dfe1902278d7b3d0786f538118433",
+ "sha256:1642907941ee4b75aacc3db688af52ea02ca2305ab22af7ee686ed726b332684",
+ "sha256:168dd6b51725a222abc722832e56624d15a63fc2e8249021509c93f1063913f6",
+ "sha256:1749f0688020209fe0d357ce1e1cd9ec9c6161ed0405ea949d24581c4c43fa91",
+ "sha256:1b3cccf75eeb5b01639b2feadb042a8e07889293b7ca72fa2985e7dcb64763cf",
+ "sha256:1b50223d92df94d54e1a31469335a2c74b16692e6c1cb726f1e6949514458706",
+ "sha256:1bc591533fc975614f7e13594daee76af96b8e1fbcf8de76c8773858fa9e7cea",
+ "sha256:1c2200b98a805351cb3142ae4e1fdcc9e91b5e20f5d30d4862b0b96f92558f4e",
+ "sha256:1c7c642a0f79c3e3cf2965475507574d3d1a50ec71060039d60cb87358667cb2",
+ "sha256:1ee523f51718e41753f04f7102bb4dc55a18d2ea5cbaceef8ec7ca08571bd428",
+ "sha256:1f3346c5c287ac3c7f38b20380f55e8768230e7252af59fabcf3b87ab21e4256",
+ "sha256:2194bf96d5f3d4e0cb65deba370ec83dda3edfba42155f9384190ed5e51ea5e2",
+ "sha256:237b8f63a2a0fcfb1ffc06e21dad23add44e6d354b2b014364a1d41e419a4dee",
+ "sha256:23a4376b4a3183cb50d4d2a3179f887a7773cc695eb2c908e551bec3221b8c60",
+ "sha256:247ece770647c0aef080561fa996f9774b4dadce2d0c42eeb98229db7dcf820d",
+ "sha256:2696bbac613f6880fed60316c298bf3091d4f8eee3ae2e9466f70bb76204fb0c",
+ "sha256:26fe6238c2d5b11ed5063b9bf4eb290624b004fd074688da6bb079bd564f10d7",
+ "sha256:2d52dc7c33c1b83082b707f6b7814dc76d2faaa2ea62bd9c5fab4b36f83c087f",
+ "sha256:2df3ca8757dc381e75e90a4d7995a6324f58a923c7145220a7b2c0231f66fddc",
+ "sha256:303121aab4b7f898058582d7962ea79d9e26e2379d7b6d8743f70f2671674481",
+ "sha256:3088dadbffa33c29e0518578430a7dff2e901a212e487aefa5faaa0dc06dad34",
+ "sha256:31d86f9e81f3e84e00131ac7c54caf5119ae4ddd82c09c31cff597c813ce1ee2",
+ "sha256:3358097d333d40657569ec1121e21043dd7d0efa10aead1b50e8b4fa83077d7b",
+ "sha256:33e270d302c95ec426dfa0f5a4e16bff2ab8d7b8a46faa4746affb05e684ac77",
+ "sha256:33fd538191f47071deef6b1f676535e2aa770f1fd150ae4cc75a34c9e930be3d",
+ "sha256:348c8f288dc961d6bbd1985c8152a3ed7a85c95df00e82320f0c5215d922a399",
+ "sha256:349775ac30372b344d2338b2a168c0a1312a644194da25b8bec476d55761a128",
+ "sha256:34ed93e20bfd98d722b902121643791eeb4b1641871e2dc63d0d4c2d93f187df",
+ "sha256:37f667dee0f867c42894b34e2a6fe26bf195c0ea4683d9d2b713db023f242c3a",
+ "sha256:3891efe3d7a531ce6da0a4a50a99dd41c75b8fd4ca19d73c86431b4db5c305f0",
+ "sha256:38c3d22129a6958846a3098d68bc8e661704461c0be4793ae28836e4690c8478",
+ "sha256:3c2445edafc300cc40feb6a25a8356a971c30cd0bf47b5349c2ad74c508343b1",
+ "sha256:3f68fe400ceec235f3e4a4b02a28c2fd2d283584a193223c921dd4c48f1d0754",
+ "sha256:3fb1d30d4b6d6e2c4a08e5ac6fffdb2b572d2cfcca15a5509cf4e7a1350f955c",
+ "sha256:41e579025a6e13a99e6d71e39c9cfc621a0dcdbbf19106325e145fa858f2d794",
+ "sha256:421b94f3ba7067958d02e38960d987756347aa150df06df11aa68ae1af78c619",
+ "sha256:427b62d62d4f967fbb10b82a3813e4875c2a6e7e7634739f17265b650c7f65a6",
+ "sha256:436e11b4dd966afe5f7f665e4cc4c5485ffe3ceb42f25a22e1701d236abf1853",
+ "sha256:43bcf2a871f28f16135545415cab3ec43904d4c80425a64598a9e6cebfb2b5ba",
+ "sha256:43e5f9169e73d0f0db33b5f6b8554bcce69ac278c966daf83d5eb4eb2f13829f",
+ "sha256:440c401e146ce64bdb3beb8ff0c84677b6f21307c28a34779071cecee5d4d70c",
+ "sha256:44ab12e8cd17d4f001769f00ad465208b4bcb897ed29e65f058f74466b57a98f",
+ "sha256:4528cf80ebbbf57d40edfb31521ae265daa6dd636d615b1cf0ac86209579e59d",
+ "sha256:45e88111ebe331de478ef8d4293efbe88f3cf8b863386c9a2357136b838e1af0",
+ "sha256:4741d42d59e4e5fa1a86c17ab9c27dc8ea459c700d91b6742fdb9138d9a516cb",
+ "sha256:4751f1d7eecae6b2d2a773630f1a7248f125c9a92a456694d03c15bceffc9d68",
+ "sha256:488ca5c5e28ef56ec4bbb12f835b3f1cbecc5f3510062e70117bc6594851932a",
+ "sha256:4972332c079d6aad69c4620a68d015a4ecb33141583f70d642cf9edf6a713763",
+ "sha256:4a252fb862b0ae2590587e625f47a0e03da05cf0205e8830b67b6596c06038b1",
+ "sha256:4a76345f5aceb4ec404918edf9c7f2b5507db864dc0d7455982009ac0890b57b",
+ "sha256:4af350bc3f329970c0e3a59af84a8a30998bf8a9167eb50cd48e59baaa1d7bec",
+ "sha256:4bbf3ff651e0f1a19beb5d0f48e0874a9bad2482a588c9d214c96ef1fff1cd9c",
+ "sha256:4e5141543c7f7fe3087500bbb4ac2845cb528a980aa91f8f1e661e2292ff4a5d",
+ "sha256:4f5e5c6df4b703afcbe9352d238a51efd97c3b91fdc3a2052e40fdacb1e7505f",
+ "sha256:515a822c73abbf6a0b7c70976d9662be342835c9d78b8dc7c023411f39c35dbc",
+ "sha256:554f87034635bcec47c5d72447bf3db7e02da1bf493a0ada010db28a76f891c6",
+ "sha256:567cbc630302a46a8ecfd943b309ccf5372bb3718f1f3762d452df30f033bcf0",
+ "sha256:57d7fa8f23908d173001c21a9e82bfc6ad997d1b6c270fb121812b7ed158891c",
+ "sha256:5adf927dca8c47fde7e683fe69efdd81bc865c4db1fb6bb00b391e2b6185207b",
+ "sha256:5b7875ac1a2edcb691f27642b8b94b904baa6bcecb7d79c72df2228ba8cb5c51",
+ "sha256:5b7979f71d06ae45a769de0699900a246d8cb632db1e8bfdc79ec019063a503c",
+ "sha256:5c2d525a3afabcd8e3549d85fc7e111fde6bc302d06a1893fe73adb79823415e",
+ "sha256:5dc434c946012e6d8a72b10f970ea30755b718251dd7591dbfdabafd3bcb21bc",
+ "sha256:5f1ea31d61bcd2cd2f3ec4ca80a64187bbd7948f490b63cf0dcbc6e717b4c1e9",
+ "sha256:62198213fc3e0c56e567894b318ba45834e007d065f84ba6dc9165d21546fc56",
+ "sha256:63aa52659bc32bb9bd7cb5caf523b4d14429a477762cfac886132d687c1f80fc",
+ "sha256:649f2682c090cca1ac4037866381f3652eaacbd56e5178030f4ce1325b8f945b",
+ "sha256:67e57b834e07ed973cee7b6da1548ff28a56458d77696fd2a5f397f340694848",
+ "sha256:684160b3c0a9b62c6f0de90f44e11dc5d8643dcfa18a5856b45fb1c47478bb71",
+ "sha256:6a8c5ce76b94ba49f3be8a8f2611abc6564210702c72ac9e237ca2bebfd17794",
+ "sha256:6a9f98af872355e0c02439e48583958eee00e60b928bb20476460d9d40cb7b4e",
+ "sha256:6c45258a37fc22721395c09927cb982d3e7a83607cab15be7e2416501bd3a330",
+ "sha256:6cbf4e21ef0890804b5bb9ad25c48f9c127758d7f6c66bef374efcacc63c738a",
+ "sha256:6cf633df84d80a1668fcf61e330791dae46825e395549e7d34f376411e75088a",
+ "sha256:6efb8f21cc136c79b3e5bb747c8682d37916fb202cdbbc32182de5c4e47f821f",
+ "sha256:70129ebb8f20e1ac1da58b78ed381624bd689a43a9a7366560bd8fabea145105",
+ "sha256:704381264b36a18b9c62ecbabe2e71d0fc58c77c129c15355c989b10bf05b6b0",
+ "sha256:7236be540d6be9ce448d98b940dd26ddf70ca41012e8a14a53fd9354cefe4e8d",
+ "sha256:72f34834518157a75e7090f328ee7a16c70c804cfc7c694fa069cc888e9fc03e",
+ "sha256:74379a577a9f3b6afbdedf1b90e5c7764467051977f18a326d7d607336d743bd",
+ "sha256:74a164e8b63f1e9cf35c9a7809d082b033d1a00e7375d5d814415436e7867e57",
+ "sha256:760de77279e9cf9c81d012ce0705cba13afccee9b09c480f17d778c8c5cefae8",
+ "sha256:764b32d52d15b8b95ac8160e540772fa1adeb611fe40bffaeb42e7bf98279e44",
+ "sha256:79a3203aadf39637869dfea1185227d8452844d78b837e54fb1117b4d34ba5c3",
+ "sha256:7c343ee174d417a44d0c3355602c0cbbfa52a04d1bbbf1723378c7d2c8f60626",
+ "sha256:7e27dbed5c4ba033919e4b4ed8dc14e029e91d14a93cd9f920d25277c7df6781",
+ "sha256:81507a68ba84c55241fb61cce1469f473a5da4205fc8ef6f698e5948eea8dd88",
+ "sha256:81664268dba92e037b740ecf37fa02f1cab4a391f93f28e35792b3341c60648f",
+ "sha256:839f58c5bd9989875be0fd28446dbf32cace2c2cd8bf2f6762acdc38a95cd1aa",
+ "sha256:83b8c2013edb5dc1f9e7268b6496130705bc48d79c86bb8817b3d210b81a5513",
+ "sha256:84df5f8da574caadbc0cb1b8866ecc2368cc941f0cd05f677756c802f370dafa",
+ "sha256:8580aab306888224074c7edeec734de0c3c5ccde65b2da4e6c9a5e28f7c0a1bd",
+ "sha256:85bdd40cb505a11e0ca04191711266c5fd696ed786ae83849955e457774edc96",
+ "sha256:85e402dab0f9acd3604539747c6fcc57dc188a18af6ab07eb8189351cd32466c",
+ "sha256:863f3d3b44110f7243e86cf994aa5c5d88f2348b6e84ab4402fadadfbf9f7da7",
+ "sha256:86b2b12bec60c678ed8f5cca0258ad93a8928ebddb6ca7732f0875afe1451d1a",
+ "sha256:87aa309a93bd5ec13f14309a305ff4e9bf74c5363fc46c264c0a22edfd5b0670",
+ "sha256:87cbdec1a7dd930079671a60b249f3ca4e773e6fbd0676e21e36fdc9dd0f3b00",
+ "sha256:87da13df72c5612771cd905a8b121e0bfea62d7659b1c92198736eb722220e83",
+ "sha256:88d87719fe6bddf117238b341c5db851f8e96ba68ad9832b450e4a43dc60b37f",
+ "sha256:8b4477edc03091f51f5309406d230851c23cf4822029e3bf40b8df53093fff1c",
+ "sha256:8b99ebaf9e816ac5069423b1367ee7e8078fbcebcf62545506bb0608d2f4f468",
+ "sha256:8ba782ca3bf1e81492611152b9a0d5264971339e95e34d69de0ac2c926be496d",
+ "sha256:8bcba9456242ebf180a04d9443812fd85ffe6bd12bda464dd116fcece8886ff3",
+ "sha256:8c9fe122444e129881afd1d4d1c7ac0d3ce2d91b68c2b40173b6025ff1c31f9a",
+ "sha256:8ec4777d92fd61a5c8fdeddab894fd65bea301a8092fb5419ec6472aa4d458d7",
+ "sha256:90cb2a1c9cc503a054a19612b48ff6e8e47805f618bdb3224a07568aad03a37e",
+ "sha256:9283d9dd6b44acad35118e2976fc763a065509e4118debdb61916ec322ed17b9",
+ "sha256:94ac8a6b8c47951173f0b67bf862bcb971bf24e493b9fbbdb0e010cbbc7d9f54",
+ "sha256:96d8de55029d42251945531f6aa7590c32b48163c66a43bf29d8657d7446a377",
+ "sha256:96dedccfb09a73a25751053a183159b88f4ee75f388df8166040c152ac0531c6",
+ "sha256:9761ff4a0ffa583fe850731ad24fe82c88cccb7a2294727db0955f3279a4cb3f",
+ "sha256:97b455de3e8b1b0b1e4594cb61a468992563f03ca264062fbb0a66b393c01d90",
+ "sha256:97b94fb29abf21f5f0bde15f7dbdd3a4aa2dc59f37026adc7b4bee8563b84375",
+ "sha256:99054b838b74d8d3995ea0d410976ae967c46207ae22d6ddfc535e809197dab9",
+ "sha256:99166cc98637e8bf550cda2aab07f4f1d5f899c45fbd721801aeabcc9d404824",
+ "sha256:9a51b061d54cda8b83e62c44458bfbf0dabbef9b975dd9649952ba5076b9f349",
+ "sha256:9b1dddc257279417d93c9e59420d49ef90aece90d7a01996db3aade74b0281b1",
+ "sha256:9c3c4b9aa9a27196b921197f7daf9e6c1412739df06a99cfa6e923879362eff6",
+ "sha256:a14578102a6081465aec9cf73c76c3cd3f79f0709bdb3b8ae7ab0b54c9d8b089",
+ "sha256:a16a3fa6936e36bb1414d16a6bd012c9033e5161b68b426805b61d895392437d",
+ "sha256:a33de7633c948ab2dc144af370a66e7e7af29b425dcd0f7e4f59689fb9391b53",
+ "sha256:a43418e1a90b4809a9caf64aeb8b0696e3e1f300a323acc1e6ee2f93ae319fcf",
+ "sha256:a4553d36cc0b7fce1f35ba8a94dfd775aa3ed12f5eab2dc3b46ac75a0706b0bb",
+ "sha256:a5b21b42a01a343096a1c018d35e9b7aec9c7065dda53ae8da071e37478b2cea",
+ "sha256:a65785e653573fcd1e33062760ab4c3c3440e8e910765018e4b6ed4ad07b54a0",
+ "sha256:a6671a8f6ea4f2101ce11fab5023a2e59391cff249fc3928cecb69d971525fd5",
+ "sha256:a69e8946e4902ea11fc1c557740cdbfe7d75c78fcc5e4324ff89a696a634357d",
+ "sha256:a6e3653df1a70b8ac4191216324242e4be2bca18c9a7c10934e1bd56dc7ca15e",
+ "sha256:a865d2d470220e659220fdb59d5b6c4422802d8d6098e1324bc4d12444798914",
+ "sha256:a949b072ea59c6eca0811ccd9e95133cc50d2afda8d464b5b077c78f78efa269",
+ "sha256:aa6ccc7f31018484d652cf52db020003433f3c9fa83189c028bd807d2adde503",
+ "sha256:ac0f291ab6485bd71f33941f9b92771318332a05d505460b41e893a549caadc0",
+ "sha256:acb31ecdd1a97fab5cd39a84ee9f515e727d319f796fec48703b8339b9998360",
+ "sha256:acf52474b2494ef66dc7e0fb6d5e2b50c18313039ad4d275fbf9f9907c804bc5",
+ "sha256:ad889d58361a26ba75f5d6a1a0da08ed4950ec4ac8a6da86e1c5ce1b95ccb43f",
+ "sha256:adbd48b30e3f82c89fb2b3e6a87cdd28d113b190a5ed0ee2dee286323ee9a621",
+ "sha256:af05a3f650220a6c59fa0ad2410249f2d2470a05225807c378fb67458693f8df",
+ "sha256:b3662719007e059abde7eddacf8517142ba076ddc7b30c807260e57d28c3c191",
+ "sha256:b3bece52127ac20044311ee73567f9f0893b5de64f9028aecc90cc740cfd525a",
+ "sha256:b4c8842fb19d78b5e8c2a52baf4c8357658cc56c62bc822b86ce0f942f28e286",
+ "sha256:b659fad79c99b0238c7ad7e9d7dbf4eebfea9097c2dba65fa0a4d18a25b29a2f",
+ "sha256:b6c1f9c59bbe593f88a0aad30be4150f15bd57bd64efb95feeabcb8e563f1ecd",
+ "sha256:bdd16718b63aa3ebd68aabb79021a40e47c81374852d41a306b9453141bbcbee",
+ "sha256:bf430c587f447a554c53768ad76b9846fe7c5632180ef6f69c4fce8b0552fbd0",
+ "sha256:bfed61996d618eb90d6eaae0178002e3466a28b06bfc557a7a3a7266378d8c5a",
+ "sha256:c09ada495567c9c9a8156c5ebcfb93be7fece0755062d738c972dcbecd0d84b5",
+ "sha256:c0e6ccc2b19ec8a726b2e26062ac71ea63e15500d6bf85910e42481844fdffc1",
+ "sha256:c101180495cb4ba3617b279a944345c53a5e73b0c150053d1fa8d8af32de9579",
+ "sha256:c10b9206753b64aa791b35b201485477525b26fdec5bf86e8364c388a03e2592",
+ "sha256:c3074db513c81f764053e3da079312ecf85a50d8350c71f4cc0105d9662a9e6c",
+ "sha256:c30dd1af66a820820398b26e0d74e7a9aa43cae705924f23ed828cd8e5c26c3d",
+ "sha256:c57963970d359a72262f7fe6be88f945e2334d4bc41462b7f08c37b0abf35ca6",
+ "sha256:c6301d92545c591ad31c3e050aa40a5f8a4c16413f1f9e6f9322c6f0f9d2b736",
+ "sha256:c6370189e8e66b7e608f533b939a9de092ddca6cce084ca0d3d414d2ed5b5d59",
+ "sha256:c6fc415b5568bd9accc7187f1729a99707330c0a67a8b9f93c1149ed573ed75d",
+ "sha256:c7484fea54964edd417cc3a104d5180562514aa7c4e2a2bc26d776ef0c4cb4a1",
+ "sha256:cba763d84b06bda2c38d5185dee76f1b9dfdc0789e96e476d9e10005526d0788",
+ "sha256:cd878d32f5c6cbce9783f8d6897561fb772211edba9dde49d85672b88ed45276",
+ "sha256:ce6d5cc94a50291d080259a126cbf1e9ba4ac861e6429d2f3cdbb1474f51945d",
+ "sha256:d0d24a4f3fb63852cd09af46ae4b7a4d00cc8b8615a046dca543786e728d1056",
+ "sha256:d1e0d1ea6e44f51808a9e8469c8afdebcdf6fa23d1ea524a0303d57d23919712",
+ "sha256:d54b8ae068af532c8cdf56abb9e09a60fbe7b10792444c9c27987bb6d3b450fa",
+ "sha256:d55bf4ef10eb09b8b6866790e083d26d087d84caa3cc0946ba87c3ca7ecaf7b7",
+ "sha256:d9f3848ffaf010bdbabdbf4c25641fa258b6227ff27bc74a4d06edef521a4873",
+ "sha256:da0264844a09b538c894e5eff25313d941deb4dedec2131b98418a71a3c9944e",
+ "sha256:da544672efd9ad76077928a3e6c5d894e52ce82d3bf14002db4a1bf17d1a36a2",
+ "sha256:daade8936c4deaaf7b01561324ce438ba4f885d717e9adc62b4d67212ad7d7bd",
+ "sha256:dd649663ddeafbfd4734eb8abae921dd5baa1242f20bda54e8bc927369ccded4",
+ "sha256:deca2a30d983d240b8375ec2ee0a4288e72042827fc61df2f7671f8467e4cb2f",
+ "sha256:e259bb7e1e2d8de6b35f430f5c7220b1c0ebf3962d1ba7ec7545980d5931edb8",
+ "sha256:e3996ff9b6f99180357024336bf5749a8ad6476a9a2523e535c5212b995b12a2",
+ "sha256:e3eba72f9bb84fe696516f4cbca68d3d74a376157e68bacddbb7f2516af61523",
+ "sha256:e4296fcc790876a8b0f297edc83d3b088457b774d8f67b4636807f8a2ec69a79",
+ "sha256:e53926e76131a74e79cc0b39fa712c227875f180afc68646bd1e1d8a17e60313",
+ "sha256:e681a6fc7e4f715252b9b5acfb30536ec7dd1f75033a32dc617e6fa95af1a3fd",
+ "sha256:e71b34978e77868cbf2d18c5206a4603f9c644dd7181bec5643bd40141d3b8c5",
+ "sha256:e8cda075b10bb3917b002c74a04f9e02b7d13b5bf732571404d51c52b11c7329",
+ "sha256:e90b4bcf1d9eb1010fdaee7c9209fb667e74c0684f3ba17f9032bd7319da90c9",
+ "sha256:e961093277ff9d42addb9dad5614dfb7800ccba07c245c39c8e9b4daa35d160c",
+ "sha256:e9701c073bd062fb6bf6be51b47186ad15f1e87feedf4ea07198e0333ec068dc",
+ "sha256:e998cb3685b92101ec5de0fb4d9485cf01e50bc418211955c55d98064664cf4c",
+ "sha256:ea5ecf800b45bdb34afe05a1d0dae1f8ea02a290e50636dccd399063f6b180f8",
+ "sha256:ec1a470c6db94ac4589c203921e89ac1bc13e796a8b1784d8135e1893559cd3b",
+ "sha256:edccc2ec58435a580f96a48a3ccae8cd0a480824119165dd90108718ad81ae6e",
+ "sha256:f00330ac7e24769e2032203f2b01794d670916b0c1799fd261340f1af9499875",
+ "sha256:f09ee747e2a5f876cc5ad56947734811828335e13b403dd8ea1e06d77a9dd48d",
+ "sha256:f18732adcc271741bd651c3e56fa519d8a237d2cccda01fe3afb226bf87f783b",
+ "sha256:f1b603d0686c99fa0879f104a74e7db58367634c6e50ba827bee9aa095e23205",
+ "sha256:f33cf0baa91eccd2cb7b62bf00f10c2264ef578b71dd33a12962e71a36eb4d32",
+ "sha256:f3e1a44af01b6692de0ec6caba5f0bf93ceb36896e02b7fc00952c6ea7ef39e1",
+ "sha256:f484ed57bb3e4142f9d6439568658c38be5f94b702ba00a1ff32c69783b6c66d",
+ "sha256:f5d031f35962e5483a613214e61f09fe24ab523062c3646d592dc16c4a217451",
+ "sha256:f6247f5e23ee94f2557ac9dab738a336f607c6ff476fcf66ca70c3aef5eee15a",
+ "sha256:f7db035447a0ac8959aa230c5d36545ecf9f547413eb1711c0ca6f0ba1418925",
+ "sha256:f83295394d34e1287e5b30fcc496c13b92cf886a131f3dae5444e38da8757efb",
+ "sha256:fac4832b638000106207bc44e44b9616a6a416aaee56c62b01d61f3705e49f58",
+ "sha256:fb59a0dd61fb2ad481c03fda399d78ce57dab6bb62c2c8fdb446a7ba4754b89a",
+ "sha256:fc737c05ca2d48e5dcdbbb249314df3fc6c2a0be6da8b0aa28e13d72afaad7cd",
+ "sha256:ff48915bf1871a1f19f74c11834c6329443d306cedc0c05fe7fe617810422a80",
+ "sha256:ffa44b4c7c5d0ffa31356b4428659516c0e47647825c74079a296b3857b6d99d"
],
- "markers": "python_version >= '3.8'",
- "version": "==3.8.1"
+ "markers": "python_version >= '3.9'",
+ "version": "==4.0.1"
},
"yarl": {
"hashes": [
@@ -3867,102 +3987,181 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380",
- "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62",
- "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c",
- "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226",
- "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5",
- "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833",
- "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b",
- "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99",
- "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501",
- "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec",
- "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698",
- "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4",
- "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a",
- "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3",
- "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2",
- "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a",
- "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e",
- "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4",
- "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419",
- "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84",
- "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da",
- "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519",
- "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe",
- "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381",
- "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29",
- "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614",
- "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0",
- "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe",
- "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29",
- "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0",
- "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917",
- "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9",
- "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32",
- "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94",
- "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63",
- "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd",
- "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198",
- "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde",
- "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012",
- "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1",
- "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15",
- "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b",
- "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993",
- "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4",
- "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5",
- "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8",
- "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35",
- "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642",
- "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2",
- "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d",
- "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9",
- "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c",
- "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33",
- "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db",
- "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf",
- "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9",
- "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee",
- "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84",
- "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44",
- "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f",
- "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9",
- "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9",
- "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177",
- "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8",
- "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b",
- "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39",
- "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41",
- "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0",
- "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616",
- "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d",
- "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba",
- "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2",
- "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b",
- "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9",
- "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b",
- "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209",
- "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48",
- "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046",
- "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632",
- "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a",
- "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2",
- "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1",
- "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b",
- "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990",
- "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5",
- "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b",
- "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9",
- "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534",
- "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81",
- "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a",
- "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d",
- "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf",
- "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"
+ "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45",
+ "sha256:012a22b88a77ca2e59b98ac5889b0deb604147666032f45e6d6e217634d2550d",
+ "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5",
+ "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b",
+ "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f",
+ "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f",
+ "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5",
+ "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22",
+ "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5",
+ "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac",
+ "sha256:13e3afe97712e8887cd516e960c63f0b93122971e5b5e4b2622fe7701771e838",
+ "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90",
+ "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626",
+ "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4",
+ "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369",
+ "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b",
+ "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e",
+ "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee",
+ "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1",
+ "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102",
+ "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8",
+ "sha256:29880d17a8eb0b5cfdfd8944b468322928059aa35f1f5fa8ff22b149ec0b42f8",
+ "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9",
+ "sha256:2e9cf9253119d8e5d111f05d71626786fd3d6193817316eab1ca088cdb8593cf",
+ "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0",
+ "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031",
+ "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e",
+ "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235",
+ "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072",
+ "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb",
+ "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c",
+ "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950",
+ "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2",
+ "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb",
+ "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e",
+ "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6",
+ "sha256:3e5e1224c0a6a90e05843e07adfec669edebec17801c67072f51e59561d63c0b",
+ "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2",
+ "sha256:433c5a81eade63b47e522303bad236f59dba55ea6951746f5558355eeed8c75d",
+ "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa",
+ "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2",
+ "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818",
+ "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032",
+ "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71",
+ "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96",
+ "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687",
+ "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8",
+ "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3",
+ "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61",
+ "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9",
+ "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1",
+ "sha256:55261ac0d2941c42f196dd576f543d87a8ee03cd6f5e30dfb4d807b2e3b9121a",
+ "sha256:56490c595a28b1bb27dfc583e816152a9767721ef58b2c03b13f954d2f707420",
+ "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4",
+ "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65",
+ "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663",
+ "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f",
+ "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591",
+ "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a",
+ "sha256:5ca0555312ae2fe82715cada7fac375530c2f3349e1eaa1bcb33d0283ac79a18",
+ "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e",
+ "sha256:5e2d0e146dcb57034f8b97dc58d2d512cb90aba253960ce449f695fec6a82c6f",
+ "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7",
+ "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3",
+ "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c",
+ "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3",
+ "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7",
+ "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96",
+ "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486",
+ "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3",
+ "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6",
+ "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b",
+ "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731",
+ "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959",
+ "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9",
+ "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf",
+ "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8",
+ "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e",
+ "sha256:789b8982559ae28dad2356519f841655756cdcd96616410590ae0b17454ee64f",
+ "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885",
+ "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0",
+ "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506",
+ "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2",
+ "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0",
+ "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e",
+ "sha256:85de3134b5379856e323ba37c19c9256d39425f7b76a63af52b09fb4664c2e8f",
+ "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e",
+ "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491",
+ "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a",
+ "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20",
+ "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449",
+ "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af",
+ "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c",
+ "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712",
+ "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7",
+ "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a",
+ "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20",
+ "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f",
+ "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3",
+ "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9",
+ "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e",
+ "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5",
+ "sha256:994e883d17c559cdfd38c84003c8b27d25424a1077272a17e7cd27bfe0bf57b2",
+ "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36",
+ "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263",
+ "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4",
+ "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11",
+ "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a",
+ "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3",
+ "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375",
+ "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa",
+ "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d",
+ "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5",
+ "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99",
+ "sha256:a951ad59cad9145664a730d3036b40b844e74d2d3683da40111463cd3a83845d",
+ "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c",
+ "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488",
+ "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6",
+ "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc",
+ "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b",
+ "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f",
+ "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00",
+ "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10",
+ "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598",
+ "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6",
+ "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962",
+ "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c",
+ "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08",
+ "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab",
+ "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573",
+ "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90",
+ "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5",
+ "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18",
+ "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d",
+ "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af",
+ "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea",
+ "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c",
+ "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b",
+ "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6",
+ "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8",
+ "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774",
+ "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004",
+ "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a",
+ "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a",
+ "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2",
+ "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2",
+ "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa",
+ "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe",
+ "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3",
+ "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc",
+ "sha256:e06efa066f7dbadbc84ebc126a97c452a6451dfcf589d89d788484949e1cf795",
+ "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d",
+ "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc",
+ "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893",
+ "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef",
+ "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d",
+ "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda",
+ "sha256:eb12fb2ba69ffa05f8695f61c69e591dc4b4a12ac3757ac8af8adb259bf56d17",
+ "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30",
+ "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7",
+ "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5",
+ "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182",
+ "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f",
+ "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9",
+ "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada",
+ "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876",
+ "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a",
+ "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348",
+ "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3",
+ "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f",
+ "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0",
+ "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f"
],
"markers": "python_version >= '3.7'",
- "version": "==3.4.9"
+ "version": "==3.5.1"
},
"distlib": {
"hashes": [
@@ -3973,11 +4172,11 @@
},
"filelock": {
"hashes": [
- "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402",
- "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3"
+ "sha256:142cd9fa77a872c5e78c62329a0d15278fadc686eb89e760017968961a4fd6b2",
+ "sha256:f6a6a28f743f9b95ce19db5abe0f376f75eb56517dff21e1a4751e2657d3e83d"
],
"markers": "python_version >= '3.10'",
- "version": "==3.32.0"
+ "version": "==3.32.5"
},
"identify": {
"hashes": [
@@ -3989,11 +4188,11 @@
},
"idna": {
"hashes": [
- "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2",
- "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"
+ "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15",
+ "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4"
],
"markers": "python_version >= '3.9'",
- "version": "==3.18"
+ "version": "==3.19"
},
"iniconfig": {
"hashes": [
@@ -4023,11 +4222,11 @@
},
"platformdirs": {
"hashes": [
- "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0",
- "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74"
+ "sha256:4f41487eeeeeb07f3a6625e61d9bc0ae6809f92d3386dbd74392fbb76108104d",
+ "sha256:8a02cb259042c79d1cd0450facc2fe6dc9d303ae7901afbe33bf8ea0b188cef6"
],
"markers": "python_version >= '3.10'",
- "version": "==4.11.0"
+ "version": "==4.11.7"
},
"pluggy": {
"hashes": [
@@ -4039,20 +4238,20 @@
},
"pre-commit": {
"hashes": [
- "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421",
- "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717"
+ "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441",
+ "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
- "version": "==4.6.1"
+ "version": "==4.6.2"
},
"pygments": {
"hashes": [
- "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f",
- "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"
+ "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9",
+ "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c"
],
"markers": "python_version >= '3.9'",
- "version": "==2.20.0"
+ "version": "==2.21.0"
},
"pytest": {
"hashes": [
@@ -4065,11 +4264,11 @@
},
"python-discovery": {
"hashes": [
- "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef",
- "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98"
+ "sha256:6393b4eae1be8b2182670635e7baff89ac21cb9f8e86fd1ff40c7b1144febb4c",
+ "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a"
],
"markers": "python_version >= '3.8'",
- "version": "==1.5.0"
+ "version": "==1.6.0"
},
"pyyaml": {
"hashes": [
@@ -4170,28 +4369,28 @@
},
"ruff": {
"hashes": [
- "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f",
- "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe",
- "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318",
- "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140",
- "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8",
- "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1",
- "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344",
- "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700",
- "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f",
- "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa",
- "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b",
- "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690",
- "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c",
- "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f",
- "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f",
- "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd",
- "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0",
- "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12"
+ "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70",
+ "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25",
+ "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39",
+ "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3",
+ "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88",
+ "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d",
+ "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953",
+ "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8",
+ "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e",
+ "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5",
+ "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876",
+ "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c",
+ "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718",
+ "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4",
+ "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757",
+ "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050",
+ "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1",
+ "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
- "version": "==0.16.2"
+ "version": "==0.16.6"
},
"the-agent": {
"editable": true,
@@ -4208,11 +4407,11 @@
},
"virtualenv": {
"hashes": [
- "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c",
- "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd"
+ "sha256:1dc49c790072a9072cb1803f9bd62aa69cd583077cada32390f75505cdc64c9b",
+ "sha256:3040eb3cbf5d32b10ffd57d167e6a162237ad82ba7d8cf1400a1efed593d85ac"
],
"markers": "python_version >= '3.9'",
- "version": "==21.7.0"
+ "version": "==21.7.8"
}
}
}
diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml
index 29e40096..b53b1fb3 100644
--- a/docs/open-api-docs.yaml
+++ b/docs/open-api-docs.yaml
@@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: The Agent's user-facing API
description: The user-facing parts of The Agent's API service (excluding system-level endpoints, chat completion, maintenance endpoints, etc.)
- version: 5.34.1
+ version: 5.35.0
license:
name: MIT
url: https://opensource.org/licenses/MIT
diff --git a/openspec/changes/archive/2026-09-10-coordinate-message-bursts/.openspec.yaml b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/.openspec.yaml
new file mode 100644
index 00000000..2e24cfa4
--- /dev/null
+++ b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-07
diff --git a/openspec/changes/archive/2026-09-10-coordinate-message-bursts/design.md b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/design.md
new file mode 100644
index 00000000..5a9210fe
--- /dev/null
+++ b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/design.md
@@ -0,0 +1,123 @@
+## Context
+
+Before this change, Telegram and WhatsApp webhook endpoints enqueued synchronous responders through FastAPI `BackgroundTasks`. Each responder created one detached SQLAlchemy session, ingested the message, constructed `ChatAgent`, and performed command, debounce, reply-decision, LLM, and send work. `ChatAgent` slept for the configured debounce delay and independently searched message history for a newer message from the same invoker.
+
+The application runs multiple service instances against one PostgreSQL database. Process-local cancellation or locking therefore cannot establish a single burst winner. Database sessions must not remain checked out while waiting or while external services block. See `proposal.md` for the behavioral motivation and `specs/message-burst-processing/spec.md` for the required behavior.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Make PostgreSQL the authority for the active burst state and deadline.
+- Keep webhook responses immediate while using a non-blocking 1 second delayed attempt.
+- Preserve a strict session boundary around each short database phase.
+- Pass a stable history cutoff and aggregate addressing decision into conversational processing.
+- Keep the coordination mechanism small enough to share between Telegram and WhatsApp responders.
+
+**Non-Goals:**
+
+- Automatic retries, periodic recovery workers, or exactly-once guarantees across process failure and external platform sends.
+- Platform-specific media album grouping.
+- Changing command behavior, group reply probability, or LLM/tool behavior beyond invoking reply logic once per settled burst.
+- Introducing Redis, a message broker, or another external dependency.
+
+## Decisions
+
+### Store one active burst mailbox per chat and author
+
+Add a coordination table with a composite primary key of `(chat_id, author_id)`. A row represents outstanding conversational work rather than a permanent event log. It contains:
+
+- a monotonically increasing `message_count`;
+- `process_after`, calculated from database time plus 1 second;
+- the final logical message cutoff;
+- an aggregate `is_addressed` flag;
+- an `is_processing` flag.
+
+On each newly ingested non-command message, an upsert increments the message count, resets `process_after`, advances the logical cutoff when appropriate, and ORs the addressing flag. The operation returns an immutable scheduled-burst value containing the mailbox identity, message count, and wait duration.
+
+This model is preferred over processing status on every chat message because the claimable unit is a coalesced burst, not each fragment. It also avoids accumulating completed queue rows. The mailbox row is deleted after successful processing when its message count has not advanced.
+
+Alternative considered: add `received`, `processing`, and `completed` to every chat message. This would require grouping and transitioning several history rows for one invocation, including a separate representation for coalesced fragments, while still needing a per-author coordination lock.
+
+### Use asynchronous delayed attempts initiated by webhooks
+
+After the ingress transaction commits and its detached session closes, the background responder creates a DI instance containing the burst invoker and chat identifiers but no database session, then invokes its `MessageBurstService`. The suspended coroutine retains that contextual service and the immutable schedule, but no SQLAlchemy session, transaction, or checked-out connection.
+
+After waking, it opens a new detached session and performs one atomic conditional claim. The claim succeeds only when:
+
+- the stored message count equals the attempt's expected message count;
+- `process_after` is no later than database time; and
+- the mailbox is not currently being processed.
+
+An attempt that does not satisfy those conditions closes its fresh session and exits. Every message may therefore create a small suspended coroutine, but older attempts become inexpensive no-ops. FastAPI continues returning the webhook response before these background attempts execute.
+
+The delayed orchestration function remains asynchronous. Existing synchronous ingestion and conversational processing execute through the thread pool rather than on the event-loop thread.
+
+Both platform responders now use the same per-scheduled-burst helper and contextual DI construction. Telegram awaits its single scheduled burst, while WhatsApp gathers the helper once for each scheduled message because one webhook may contain multiple messages.
+
+Alternative considered: a periodic database poller. It would make recovery stronger, but it is unnecessary for the current scope and adds a continuously running subsystem. Outstanding rows can be inspected directly in the database when needed.
+
+### Separate database phases from waiting and external work
+
+The flow uses explicit resource boundaries:
+
+1. Open a detached session, ingest the message, classify commands/addressing, upsert the burst mailbox, commit, and close the session.
+2. Create a DI instance containing the burst invoker and chat identifiers but no database session, then await the 1 second timer.
+3. Clone that contextual DI with a fresh detached session, conditionally claim the burst and its cutoff, commit, and close the session.
+4. Load the bounded inputs needed for processing with fresh database access, then release that transaction before LLM, media, or platform network calls.
+5. Clone the contextual DI with another short fresh transaction to finalize the mailbox after the response path completes.
+
+The claim session is never passed into `ChatAgent`. A `MessageBurstService` created from the cloned contextual DI loads the claimed message and owns the shared processing path, preserving the resource-release discipline around long-running external operations.
+
+### Preserve pending work that arrives during processing
+
+A new message may arrive after an earlier message count has been claimed. Its upsert still increments the current message count and establishes a new quiet deadline without modifying the claimed cutoff.
+
+When processing finishes, finalization compares the current message count with the claimed message count:
+
+- If they match, delete the mailbox row.
+- If they differ, clear the processing flag while retaining the newer messages and return a new scheduled-burst value for the existing deadline, with no delay if that deadline has already passed.
+
+This completion handoff prevents newer messages from being stranded when their own delayed attempt woke while the preceding burst was still processing.
+
+### Determine reply eligibility once per settled burst
+
+Commands are recognized before burst upsert and continue through their immediate path. They neither wait nor change an existing conversational burst deadline.
+
+For non-command messages, ingress contributes whether that individual message explicitly addressed the bot. The mailbox retains the logical OR across the active burst. On claim, `ChatAgent` evaluates the existing `should_reply` behavior once using the claimed cutoff and addressed state:
+
+- private chats are addressed;
+- group bursts with any explicit tag are addressed even if the final fragment is untagged;
+- untagged group bursts retain the existing probabilistic/non-mention decision behavior.
+
+Responders classify commands and record conversational messages; `MessageBurstService` owns burst coordination, claimed-message hydration, and the shared processing path. Conversational decision logic remains in `ChatAgent`.
+
+### Add a deterministic history cutoff
+
+Add a database-generated monotonic order column to `chat_messages`. Logical history order uses platform `sent_at` followed by this database order as a tie-breaker. The mailbox stores the final cutoff tuple for the burst, and history loading for a claimed burst is bounded by that tuple.
+
+Every arrival resets the quiet deadline, including a late-delivered message with an earlier platform timestamp. The stored cutoff advances only to the greatest logical message tuple, allowing earlier late-delivered fragments to be included without moving the invocation past logically later chat messages.
+
+This replaces the current ambiguous same-timestamp behavior, where differing message IDs can each be treated as newer depending on which handler evaluates them.
+
+### Keep successful mailbox state ephemeral
+
+Successful finalization deletes the mailbox row when its message count has not advanced.
+
+No `completed` queue state is retained, and outstanding mailbox rows remain directly inspectable.
+
+## Risks / Trade-offs
+
+- [A platform delivery gap exceeds 1 second] โ The later delivery forms another burst; the threshold may need adjustment if this is observed operationally.
+- [One coroutine is scheduled per delivered message] โ Coroutines sleep without threads or database resources, and obsolete schedules perform one short conditional check before exiting.
+- [A process exits after accepting a webhook] โ Outstanding mailbox state remains in the database, but automatic recovery is outside this change.
+- [A process exits after sending a reply but before finalizing the mailbox] โ Exactly-once recovery across the external send boundary is explicitly outside scope.
+- [Active processing overlaps newer messages] โ Retain the newer messages in the mailbox and schedule them when active processing finalizes.
+
+## Migration Plan
+
+1. Add the deterministic message-order column and active burst mailbox model, including model imports in `src/db/alembic/env.py`.
+2. Have the user generate the Alembic migration with `./tools/db_generate_migration -y` and review its backfill, constraints, foreign keys, and indexes.
+3. Deploy the schema before application instances that reference the new coordination table.
+4. Deploy application instances with shared burst coordination and remove the old debounce path in the same application release.
+5. If rollback is required, restore the old application version before removing the new table or message-order column; the additional schema is otherwise harmless to the old code.
diff --git a/openspec/changes/archive/2026-09-10-coordinate-message-bursts/proposal.md b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/proposal.md
new file mode 100644
index 00000000..3c90faf4
--- /dev/null
+++ b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/proposal.md
@@ -0,0 +1,31 @@
+## Why
+
+Concurrent webhook handlers currently make independent timing and history decisions for messages that may belong to one logical user burst. Across multiple service instances, this can drop the intended final response, answer an earlier fragment, or process incomplete photo-and-prompt context.
+
+## What Changes
+
+- Group non-command messages into bursts per chat and author using a 1 second quiet period.
+- Persist the active burst deadline and message count in the shared database so all service instances make one coordinated processing decision.
+- Schedule non-blocking asynchronous wake-ups without retaining a database session or transaction during the quiet period.
+- Atomically allow only the wake-up matching the current settled message count to claim processing.
+- In group chats, reply once when any message in the settled burst addressed the bot; bursts from different authors remain independent.
+- In private chats, treat every settled burst as addressed to the bot and reply once using all messages through the burst cutoff.
+- Keep commands outside burst processing so they receive an immediate response.
+- Remove the existing per-message sleep-and-supersession behavior.
+
+## Capabilities
+
+### New Capabilities
+
+- `message-burst-processing`: Defines quiet-period burst formation, cross-instance claiming, command bypass, reply eligibility, context boundaries, and resource-release behavior.
+
+### Modified Capabilities
+
+None.
+
+## Impact
+
+- Affects Telegram and WhatsApp inbound message handling, chat-agent invocation, chat-history loading, dependency injection, configuration, and database models/repositories.
+- Requires a database migration for shared burst coordination and deterministic message cutoff data.
+- Changes non-command reply timing to begin after 1 second without a message from the same author in the same chat.
+- Does not add automatic retry or continuous recovery processing; outstanding work remains visible in the database.
diff --git a/openspec/changes/archive/2026-09-10-coordinate-message-bursts/specs/message-burst-processing/spec.md b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/specs/message-burst-processing/spec.md
new file mode 100644
index 00000000..ba4605e3
--- /dev/null
+++ b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/specs/message-burst-processing/spec.md
@@ -0,0 +1,83 @@
+## Purpose
+
+Define how incoming conversational messages are combined into user bursts and processed exactly once across service instances without delaying commands or retaining database resources while waiting.
+
+## ADDED Requirements
+
+### Requirement: Messages form per-author quiet-period bursts
+The system SHALL group non-command messages by chat and author. A burst SHALL become eligible for processing only after 1 second has elapsed since the most recently received non-command message for that chat and author.
+
+#### Scenario: Several messages arrive within the quiet period
+- **WHEN** multiple non-command messages from one author in one chat arrive with less than 1 second of silence between them
+- **THEN** the system treats them as one burst and starts no response processing until 1 second after the last message arrives
+
+#### Scenario: A later message arrives after the quiet period
+- **WHEN** a message arrives more than 1 second after the preceding burst became eligible and was claimed
+- **THEN** the system treats the later message as part of a new burst
+
+#### Scenario: Different authors send messages concurrently
+- **WHEN** messages from different authors arrive in the same group chat
+- **THEN** each author's quiet period and burst are tracked independently
+
+### Requirement: Burst claiming is coordinated across instances
+The system SHALL use shared database state to ensure that no more than one service instance claims a settled burst for response processing.
+
+#### Scenario: Multiple delayed attempts wake for one burst
+- **WHEN** delayed attempts created by several messages or service instances wake for the same chat and author
+- **THEN** only the attempt matching the current settled message count claims response processing
+
+#### Scenario: An obsolete attempt wakes
+- **WHEN** a delayed attempt wakes after a newer message has extended the burst deadline
+- **THEN** the obsolete attempt performs no response processing
+
+### Requirement: Waiting does not retain database resources
+The system SHALL commit and release the database session and transaction used to record a message before waiting for the quiet period. The delayed attempt SHALL acquire a fresh database session only after its asynchronous wait completes.
+
+#### Scenario: A burst is waiting for additional messages
+- **WHEN** a delayed attempt is suspended during the 1 second quiet period
+- **THEN** that attempt holds no database transaction or checked-out database connection
+
+### Requirement: Commands bypass burst processing
+The system SHALL process each recognized command immediately without waiting for the burst quiet period. A command SHALL NOT extend, replace, or be claimed as part of a conversational burst.
+
+#### Scenario: A command arrives without other messages
+- **WHEN** the system receives a recognized command
+- **THEN** command processing begins without a burst delay
+
+#### Scenario: A command arrives during a conversational burst
+- **WHEN** a recognized command arrives while non-command messages from the same author are waiting for their quiet period
+- **THEN** the command is processed immediately and the existing conversational burst retains its own deadline
+
+### Requirement: Group-chat reply eligibility is evaluated per burst
+For a group chat, the system SHALL preserve the existing reply-decision behavior while evaluating it once for each settled author burst. If any message in the burst explicitly addresses the bot, the burst SHALL be treated as explicitly addressed even when the final burst message does not repeat the tag.
+
+#### Scenario: A tagged message is followed by untagged fragments
+- **WHEN** an author sends a tagged message followed within the same burst by untagged attachments or text
+- **THEN** the system evaluates and processes one explicitly addressed burst containing all of those messages
+
+#### Scenario: Two authors tag the bot
+- **WHEN** two authors independently send tagged bursts in one group chat
+- **THEN** each author's settled burst remains eligible for its own reply
+
+#### Scenario: A burst does not tag the bot
+- **WHEN** no message in a settled group-chat burst explicitly addresses the bot
+- **THEN** the existing non-mention reply-decision behavior is applied once to that burst
+
+### Requirement: Private-chat messages are evaluated as one addressed burst
+The system SHALL treat every settled private-chat burst as explicitly addressed and SHALL produce at most one response for that burst.
+
+#### Scenario: Photos and a prompt arrive as separate deliveries
+- **WHEN** a private-chat user sends multiple photos and prompt text within one burst
+- **THEN** the system invokes conversational processing once with all burst messages available as context
+
+### Requirement: Processing uses a stable burst cutoff
+The system SHALL evaluate a burst using chat history through the burst's final logical message and SHALL exclude messages logically after that cutoff. Message ordering SHALL be deterministic when platform timestamps are equal or deliveries arrive out of order.
+
+#### Scenario: Another message arrives after a burst is claimed
+- **WHEN** a burst has been claimed and a later message is received
+- **THEN** the claimed processing uses its original cutoff and the later message is assigned to subsequent processing
+
+#### Scenario: Messages have equal platform timestamps
+- **WHEN** two stored messages have equal platform timestamps
+- **THEN** the system applies a stable database-backed ordering to determine the burst cutoff and history order
+
diff --git a/openspec/changes/archive/2026-09-10-coordinate-message-bursts/tasks.md b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/tasks.md
new file mode 100644
index 00000000..b6a792c1
--- /dev/null
+++ b/openspec/changes/archive/2026-09-10-coordinate-message-bursts/tasks.md
@@ -0,0 +1,36 @@
+## 1. Persistence and Ordering
+
+- [x] 1.1 Add deterministic ingestion ordering to `ChatMessageDB`, its domain mapper, repository ordering, and bounded-history queries.
+- [x] 1.2 Add the active per-chat/per-author burst mailbox model and repository operations for upsert, conditional claim, and message-count-aware finalization.
+- [x] 1.3 Register the active burst mailbox model in `src/db/alembic/env.py`.
+- [x] 1.4 Generate and review the Alembic migration, including existing-row identity population, reversible constraints, foreign keys, and indexes.
+
+## 2. Burst Orchestration
+
+- [x] 2.1 Add shared burst orchestration that records a 1 second database deadline and returns an immutable scheduled-burst value.
+- [x] 2.2 Add the asynchronous delayed attempt using the scheduled wait duration, with a context-only DI retaining the burst invoker and chat identifiers, no database session or transaction retained across the wait, and fresh short sessions for claim and finalization.
+- [x] 2.3 Run synchronous ingestion and conversational processing through the thread pool so delayed orchestration never blocks the event-loop thread.
+- [x] 2.4 Preserve newer messages received during active processing and schedule them after the earlier claim finalizes.
+
+## 3. Inbound Platform Integration
+
+- [x] 3.1 Integrate Telegram ingestion with command bypass, per-message addressing contribution, burst scheduling, and bounded conversational processing.
+- [x] 3.2 Integrate WhatsApp ingestion with the same shared command and burst lifecycle.
+- [x] 3.3 Ensure commands process immediately without creating or extending a conversational burst.
+
+## 4. Chat-Agent Cutover
+
+- [x] 4.1 Pass the claimed burst cutoff and aggregate addressed state into chat history and reply-decision behavior.
+- [x] 4.2 Evaluate group non-mention behavior once per settled burst while treating private bursts and explicitly addressed group bursts as addressed.
+- [x] 4.3 Remove `ChatAgent` sleep, per-message supersession comparison, ambiguous equal-timestamp handling, and all obsolete callers and tests.
+- [x] 4.4 Replace the old debounce configuration path with the 1 second burst quiet-period configuration and update existing configuration tests.
+
+## 5. Verification and Cleanup
+
+- [x] 5.1 Extend existing repository tests for upsert, one-winner conditional claims, message-count-aware finalization, and deterministic cutoff ordering.
+- [x] 5.2 Extend existing service, responder, and chat-agent tests for timer reset, obsolete timer no-op, resource release during sleep, command immediacy, group author isolation, group addressing aggregation, and private photo-plus-prompt bursts.
+- [x] 5.3 Run a concurrent PostgreSQL scenario with two service-level claim attempts and verify that only the settled message count invokes conversational processing.
+- [x] 5.4 Run the focused changed test modules, complete test suite, Ruff, and spacing checker on every changed Python file.
+- [x] 5.5 Remove obsolete debounce helpers and confirm API documentation requires no behavior-contract update beyond the OpenSpec capability.
+- [x] 5.6 Normalize PostgreSQL database timestamps to the mailbox timestamp domain and verify newer-message finalization against PostgreSQL.
+- [x] 5.7 Increase the default burst quiet period to 1 second and verify late delivery remains within one settled burst.
diff --git a/pyproject.toml b/pyproject.toml
index 3f22cdeb..0d8ac060 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "the-agent"
-version = "5.34.1"
+version = "5.35.0"
[tool.setuptools]
package-dir = {"" = "src"}
diff --git a/src/db/alembic/env.py b/src/db/alembic/env.py
index 9aca7aaa..81e9b06e 100644
--- a/src/db/alembic/env.py
+++ b/src/db/alembic/env.py
@@ -19,6 +19,9 @@
# noinspection PyUnresolvedReferences
from db.model.chat_message import ChatMessageDB # used by alembic # noqa: F401
+# noinspection PyUnresolvedReferences
+from db.model.chat_message_burst import ChatMessageBurstDB # used by alembic # noqa: F401
+
# noinspection PyUnresolvedReferences
from db.model.price_alert import PriceAlertDB # used by alembic # noqa: F401
diff --git a/src/db/alembic/versions/c731e1e75917_burst_message_queues.py b/src/db/alembic/versions/c731e1e75917_burst_message_queues.py
new file mode 100644
index 00000000..ee7b4ce6
--- /dev/null
+++ b/src/db/alembic/versions/c731e1e75917_burst_message_queues.py
@@ -0,0 +1,45 @@
+"""burst_message_queues
+
+Revision ID: c731e1e75917
+Revises: a5fee1e83958
+Create Date: 2026-09-10 15:29:52.204294
+
+"""
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "c731e1e75917"
+down_revision: Union[str, None] = "a5fee1e83958"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table("chat_message_bursts",
+ sa.Column("chat_id", sa.UUID(), nullable = False),
+ sa.Column("author_id", sa.UUID(), nullable = False),
+ sa.Column("message_count", sa.BigInteger(), nullable = False),
+ sa.Column("process_after", sa.DateTime(), nullable = False),
+ sa.Column("last_message_sent_at", sa.DateTime(), nullable = False),
+ sa.Column("last_message_ingestion_order", sa.BigInteger(), nullable = False),
+ sa.Column("is_addressed", sa.Boolean(), nullable = False),
+ sa.Column("is_processing", sa.Boolean(), nullable = False),
+ sa.ForeignKeyConstraint(["author_id"], ["simulants.id"], name = "chat_message_bursts_author_id_fkey", ondelete = "CASCADE"),
+ sa.ForeignKeyConstraint(["chat_id"], ["chat_configs.chat_id"], name = "chat_message_bursts_chat_id_fkey", ondelete = "CASCADE"),
+ sa.PrimaryKeyConstraint("chat_id", "author_id", name = "pk_chat_message_burst"),
+ )
+ op.add_column("chat_messages", sa.Column("ingestion_order", sa.BigInteger(), sa.Identity(always = False), nullable = False))
+ op.create_unique_constraint("uq_chat_messages_ingestion_order", "chat_messages", ["ingestion_order"])
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint("uq_chat_messages_ingestion_order", "chat_messages", type_ = "unique")
+ op.drop_column("chat_messages", "ingestion_order")
+ op.drop_table("chat_message_bursts")
+ # ### end Alembic commands ###
diff --git a/src/db/model/chat_message.py b/src/db/model/chat_message.py
index aadc13d4..ac481382 100644
--- a/src/db/model/chat_message.py
+++ b/src/db/model/chat_message.py
@@ -1,4 +1,14 @@
-from sqlalchemy import Boolean, Column, DateTime, ForeignKeyConstraint, PrimaryKeyConstraint, String, UniqueConstraint
+from sqlalchemy import (
+ BigInteger,
+ Boolean,
+ Column,
+ DateTime,
+ ForeignKeyConstraint,
+ Identity,
+ PrimaryKeyConstraint,
+ String,
+ UniqueConstraint,
+)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from sqlalchemy.sql import text as sqlalchemy_text
@@ -16,6 +26,7 @@ class ChatMessageDB(BaseModel):
sent_at = Column(DateTime, default = func.now(), nullable = False)
text = Column(EncryptedString, nullable = False)
is_temporary = Column(Boolean, nullable = False, default = False, server_default = sqlalchemy_text("false"))
+ ingestion_order = Column(BigInteger, Identity(), nullable = False, unique = True)
__table_args__ = (
PrimaryKeyConstraint(chat_id, message_id, name = "pk_chat_message"),
diff --git a/src/db/model/chat_message_burst.py b/src/db/model/chat_message_burst.py
new file mode 100644
index 00000000..36bd063d
--- /dev/null
+++ b/src/db/model/chat_message_burst.py
@@ -0,0 +1,33 @@
+from sqlalchemy import BigInteger, Boolean, Column, DateTime, ForeignKeyConstraint, PrimaryKeyConstraint
+from sqlalchemy.dialects.postgresql import UUID
+
+from db.model.base import BaseModel
+
+
+class ChatMessageBurstDB(BaseModel):
+ __tablename__ = "chat_message_bursts"
+
+ chat_id = Column(UUID(as_uuid = True), nullable = False)
+ author_id = Column(UUID(as_uuid = True), nullable = False)
+ message_count = Column(BigInteger, nullable = False)
+ process_after = Column(DateTime, nullable = False)
+ last_message_sent_at = Column(DateTime, nullable = False)
+ last_message_ingestion_order = Column(BigInteger, nullable = False)
+ is_addressed = Column(Boolean, nullable = False)
+ is_processing = Column(Boolean, nullable = False)
+
+ __table_args__ = (
+ PrimaryKeyConstraint(chat_id, author_id, name = "pk_chat_message_burst"),
+ ForeignKeyConstraint(
+ [chat_id],
+ ["chat_configs.chat_id"],
+ name = "chat_message_bursts_chat_id_fkey",
+ ondelete = "CASCADE",
+ ),
+ ForeignKeyConstraint(
+ [author_id],
+ ["simulants.id"],
+ name = "chat_message_bursts_author_id_fkey",
+ ondelete = "CASCADE",
+ ),
+ )
diff --git a/src/di/di.py b/src/di/di.py
index bfe175c4..249a16c5 100644
--- a/src/di/di.py
+++ b/src/di/di.py
@@ -61,6 +61,8 @@
from features.chat.membership.chat_membership_repo import ChatMembershipRepository
from features.chat.membership.chat_membership_service import ChatMembershipService
from features.chat.message.chat_message_repo import ChatMessageRepository
+ from features.chat.message_burst_repo import ChatMessageBurstRepository
+ from features.chat.message_burst_service import MessageBurstService
from features.chat.telegram.sdk.telegram_bot_api import TelegramBotAPI
from features.chat.telegram.sdk.telegram_bot_sdk import TelegramBotSDK
from features.chat.telegram.telegram_chat_inbound_service import TelegramChatInboundService
@@ -124,6 +126,7 @@ class DI:
_chat_membership_repo: "ChatMembershipRepository | None"
_chat_membership_service: "ChatMembershipService | None"
_chat_message_repo: "ChatMessageRepository | None"
+ _chat_message_burst_repo: "ChatMessageBurstRepository | None"
_chat_attachment_repo: "ChatAttachmentRepository | None"
_sponsorship_repo: "SponsorshipRepository | None"
_tools_cache_repo: "ToolsCacheRepository | None"
@@ -132,6 +135,7 @@ class DI:
_purchase_record_repo: "PurchaseRecordRepository | None"
# Services
_cleanup_service: "CleanupService | None"
+ _message_burst_service: "MessageBurstService | None"
_chat_attachment_service: "ChatAttachmentService | None"
_attachment_storage: "AttachmentStorage | None"
_sponsorship_service: "SponsorshipService | None"
@@ -187,6 +191,7 @@ def __init__(
self._chat_membership_repo = None
self._chat_membership_service = None
self._chat_message_repo = None
+ self._chat_message_burst_repo = None
self._chat_attachment_repo = None
self._sponsorship_repo = None
self._tools_cache_repo = None
@@ -195,6 +200,7 @@ def __init__(
self._purchase_record_repo = None
# Services
self._cleanup_service = None
+ self._message_burst_service = None
self._chat_attachment_service = None
self._attachment_storage = None
self._sponsorship_service = None
@@ -396,6 +402,13 @@ def chat_message_repo(self) -> "ChatMessageRepository":
self._chat_message_repo = ChatMessageRepository(self.db)
return self._chat_message_repo
+ @property
+ def chat_message_burst_repo(self) -> "ChatMessageBurstRepository":
+ if self._chat_message_burst_repo is None:
+ from features.chat.message_burst_repo import ChatMessageBurstRepository
+ self._chat_message_burst_repo = ChatMessageBurstRepository(self.db)
+ return self._chat_message_burst_repo
+
@property
def chat_attachment_repo(self) -> "ChatAttachmentRepository":
if self._chat_attachment_repo is None:
@@ -447,6 +460,13 @@ def cleanup_service(self) -> "CleanupService":
self._cleanup_service = CleanupService(self)
return self._cleanup_service
+ @property
+ def message_burst_service(self) -> "MessageBurstService":
+ if self._message_burst_service is None:
+ from features.chat.message_burst_service import MessageBurstService
+ self._message_burst_service = MessageBurstService(self)
+ return self._message_burst_service
+
@property
def chat_attachment_service(self) -> "ChatAttachmentService":
if self._chat_attachment_service is None:
@@ -827,11 +847,21 @@ def chat_agent(
self,
trigger_message_text: str,
trigger_message_id: str,
- trigger_message_sent_at: datetime,
+ cutoff_sent_at: datetime,
+ cutoff_ingestion_order: int,
+ explicitly_addressed: bool,
configured_tool: ConfiguredTool | None,
) -> "ChatAgent":
from features.chat.chat_agent import ChatAgent
- return ChatAgent(trigger_message_text, trigger_message_id, trigger_message_sent_at, configured_tool, self)
+ return ChatAgent(
+ trigger_message_text = trigger_message_text,
+ trigger_message_id = trigger_message_id,
+ configured_tool = configured_tool,
+ di = self,
+ cutoff_sent_at = cutoff_sent_at,
+ cutoff_ingestion_order = cutoff_ingestion_order,
+ explicitly_addressed = explicitly_addressed,
+ )
def web_fetcher(
self,
diff --git a/src/features/chat/chat_agent.py b/src/features/chat/chat_agent.py
index be55fc09..87a918fe 100644
--- a/src/features/chat/chat_agent.py
+++ b/src/features/chat/chat_agent.py
@@ -1,5 +1,4 @@
import random
-import time
from dataclasses import dataclass
from datetime import datetime
from typing import Any, TypeVar
@@ -10,7 +9,6 @@
from db.model.chat_config import ChatConfigDB
from di.di import DI
-from features.chat.command_processor import is_known_command
from features.chat.message.chat_message import ChatMessage
from features.chat.message.formatted_chat_message import ATTACHMENT_PLACEHOLDER_REGEX
from features.external_tools.configured_tool import ConfiguredTool
@@ -18,7 +16,6 @@
from features.integrations import prompt_resolvers
from features.integrations.integrations import resolve_agent_user, resolve_external_handle, resolve_private_chat_id
from util import log
-from util.config import config
from util.error_codes import (
LLM_UNEXPECTED_RESPONSE,
NOT_CHAT_MEMBER,
@@ -42,8 +39,7 @@ class CommandHandlingResult:
__messages: list[BaseMessage]
__trigger_message_text: str # excludes the resolver formatting
- __trigger_message_id: str
- __trigger_message_sent_at: datetime
+ __explicitly_addressed: bool
__configured_tool: ConfiguredTool | None
__max_iterations: int
__di: DI
@@ -52,7 +48,9 @@ def __init__(
self,
trigger_message_text: str,
trigger_message_id: str,
- trigger_message_sent_at: datetime,
+ cutoff_sent_at: datetime,
+ cutoff_ingestion_order: int,
+ explicitly_addressed: bool,
configured_tool: ConfiguredTool | None,
di: DI,
):
@@ -65,7 +63,7 @@ def __init__(
# initialize the basic properties
self.__trigger_message_text = trigger_message_text
self.__trigger_message_id = trigger_message_id
- self.__trigger_message_sent_at = trigger_message_sent_at
+ self.__explicitly_addressed = explicitly_addressed
self.__max_iterations = invoker_membership.max_iterations
self.__configured_tool = configured_tool
self.__di = di
@@ -74,6 +72,8 @@ def __init__(
past_messages = di.chat_message_repo.get_latest_by_chat(
chat_id = target_chat.chat_id,
limit = invoker_membership.max_chat_history_depth,
+ cutoff_sent_at = cutoff_sent_at,
+ cutoff_ingestion_order = cutoff_ingestion_order,
)
langchain_messages = [self.__map_to_langchain(di, message, chat_type) for message in past_messages][::-1]
system_prompt = prompt_resolvers.chat(
@@ -102,35 +102,6 @@ def __add_message(self, message: TMessage) -> TMessage:
def __last_message(self) -> BaseMessage:
return self.__messages[-1]
- def __is_superseded_by_newer_invoker_message(self) -> bool:
- if config.chat_debounce_delay_s <= 0.0:
- return False
- self.__di.rollback_db_session() # we release the DB before sleeping
- time.sleep(config.chat_debounce_delay_s)
- chat_id = self.__di.require_invoker_chat().chat_id
- # iterate newest-to-oldest, skipping messages from other authors, to find the
- # most recent message from this invoker - only the same author messages form a burst
- try:
- recent_messages = self.__di.chat_message_repo.get_latest_by_chat(chat_id, limit = 10)
- for message in recent_messages:
- if message.author_id == self.__di.invoker.id and self.__is_newer_message(message):
- log.d(f"Message burst detected: skipping message '{self.__trigger_message_id}'")
- return True
- return False
- finally:
- self.__di.rollback_db_session()
-
- def __is_newer_message(self, message: ChatMessage) -> bool:
- if message.sent_at > self.__trigger_message_sent_at:
- return True
- if message.sent_at < self.__trigger_message_sent_at:
- return False
- if message.message_id == self.__trigger_message_id:
- return False
- if message.message_id.isdigit() and self.__trigger_message_id.isdigit():
- return int(message.message_id) > int(self.__trigger_message_id)
- return True
-
def __route_error_to_user(self, error_text: str, emoji: str = "๐คฏ") -> AIMessage:
fallback = AIMessage(prompt_resolvers.simple_chat_error(error_text, emoji = emoji))
try:
@@ -153,18 +124,6 @@ def execute(self) -> AIMessage | None:
if not self.__is_dispatchable():
return None
- # commands run eagerly when the bot is directly addressed, so a later
- # message in the same burst does not swallow the command
- if self.__is_addressable():
- command_handling = self.process_commands()
- if command_handling.is_handled:
- return command_handling.reply
-
- # burst gate: only the latest message in a burst reaches LLM processing
- if self.__is_superseded_by_newer_invoker_message():
- return None
-
- # full reply decision runs on the burst winner with burst-aware mention
if not self.should_reply():
return None
@@ -308,64 +267,7 @@ def __is_dispatchable(self) -> bool:
is_not_recursive = invoker_handle != agent_handle
return has_content and is_not_recursive
- def __is_addressable(self) -> bool:
- chat_type = self.__di.require_invoker_chat_type()
- agent_user = resolve_agent_user(chat_type)
- agent_handle = resolve_external_handle(agent_user, chat_type)
- trigger_message_text = self.__non_quoted_text(self.__trigger_message_text)
- is_bot_mentioned = bool(agent_handle) and f"@{agent_handle}" in trigger_message_text
- return self.__di.require_invoker_chat().is_private or is_bot_mentioned
-
- @staticmethod
- def __non_quoted_text(text: str) -> str:
- return "\n".join(
- line
- for line in text.splitlines()
- if not line.lstrip().startswith(">>")
- )
-
- def __has_unanswered_bot_mention(self, agent_handle: str | None) -> bool:
- if not agent_handle:
- return False
- mention_token = f"@{agent_handle}"
- if mention_token in self.__non_quoted_text(self.__trigger_message_text):
- return True
- if config.chat_debounce_delay_s <= 0.0:
- return False
- invoker_user = self.__di.invoker
- invoker_chat = self.__di.require_invoker_chat()
- chat_type = self.__di.require_invoker_chat_type()
- agent_user = resolve_agent_user(chat_type)
- agent_user_id = agent_user.id if agent_user else None
- recent_messages = self.__di.chat_message_repo.get_latest_by_chat(
- chat_id = invoker_chat.chat_id,
- limit = config.chat_history_depth,
- )
- # walk back through recent messages from the same invoker looking for an
- # unanswered @mention. a bot reply is the only true chain-break (it means
- # the prior mention was already answered). messages from other users are
- # simply skipped โ they don't answer the mention and don't break the chain.
- # known commands are self-contained โ their @-tag is syntax, not conversation โ
- # so we skip them rather than treating their tag as a pending mention.
- for message in recent_messages:
- if message.message_id == self.__trigger_message_id:
- continue
- if message.author_id == agent_user_id:
- return False
- if message.author_id != invoker_user.id:
- continue
- message_text = self.__non_quoted_text(message.text)
- if is_known_command(message_text, agent_handle):
- continue
- if mention_token in message_text:
- return True
- return False
-
def should_reply(self) -> bool:
- chat_type = self.__di.require_invoker_chat_type()
- agent_user = resolve_agent_user(chat_type)
- agent_handle = resolve_external_handle(agent_user, chat_type)
- is_bot_mentioned = self.__has_unanswered_bot_mention(agent_handle)
invoker_chat = self.__di.require_invoker_chat()
if invoker_chat.reply_chance_percent == 100:
should_reply_at_random = True
@@ -374,13 +276,15 @@ def should_reply(self) -> bool:
else:
should_reply_at_random = random.randint(0, 100) <= invoker_chat.reply_chance_percent
should_reply = (
- invoker_chat.is_private or is_bot_mentioned or should_reply_at_random
+ invoker_chat.is_private
+ or self.__explicitly_addressed
+ or should_reply_at_random
)
log.d(
f"Reply decision: {'REPLYING' if should_reply else 'NOT REPLYING'}. Conditions:\n"
- f" ยท is_private_chat = {invoker_chat.is_private}\n"
- f" ยท is_bot_mentioned = {is_bot_mentioned}\n"
- f" ยท reply_at_random = {should_reply_at_random}\n"
- f" ยท reply_chance = {invoker_chat.reply_chance_percent}%",
+ f" ยท is_private_chat = {invoker_chat.is_private}\n"
+ f" ยท explicitly_addressed = {self.__explicitly_addressed}\n"
+ f" ยท reply_at_random = {should_reply_at_random}\n"
+ f" ยท reply_chance = {invoker_chat.reply_chance_percent}%",
)
return should_reply
diff --git a/src/features/chat/message/chat_message.py b/src/features/chat/message/chat_message.py
index 6c9fdbd8..87e1802f 100644
--- a/src/features/chat/message/chat_message.py
+++ b/src/features/chat/message/chat_message.py
@@ -12,3 +12,4 @@ class ChatMessage:
author_id: UUID | None = None
sent_at: datetime = field(default_factory = datetime.now)
is_temporary: bool = False
+ ingestion_order: int | None = None
diff --git a/src/features/chat/message/chat_message_mapper.py b/src/features/chat/message/chat_message_mapper.py
index 3d5e7923..68bd9e17 100644
--- a/src/features/chat/message/chat_message_mapper.py
+++ b/src/features/chat/message/chat_message_mapper.py
@@ -11,6 +11,7 @@ def domain(db_model: ChatMessageDB | None) -> ChatMessage | None:
return None
return ChatMessage(
+ ingestion_order = db_model.ingestion_order,
chat_id = db_model.chat_id,
message_id = db_model.message_id,
author_id = db_model.author_id,
@@ -25,6 +26,7 @@ def db(domain_model: ChatMessage | None) -> ChatMessageDB | None:
return None
return ChatMessageDB(
+ ingestion_order = domain_model.ingestion_order,
chat_id = domain_model.chat_id,
message_id = domain_model.message_id,
author_id = domain_model.author_id,
diff --git a/src/features/chat/message/chat_message_repo.py b/src/features/chat/message/chat_message_repo.py
index 2ed0fb71..e6db2635 100644
--- a/src/features/chat/message/chat_message_repo.py
+++ b/src/features/chat/message/chat_message_repo.py
@@ -1,7 +1,7 @@
from datetime import datetime
from uuid import UUID
-from sqlalchemy import desc
+from sqlalchemy import and_, desc, or_
from sqlalchemy.orm import Session
from db.model.chat_message import ChatMessageDB
@@ -41,13 +41,39 @@ def get_latest_by_chat(
skip: int = 0,
limit: int = 100,
include_temporary: bool = False,
+ cutoff_sent_at: datetime | None = None,
+ cutoff_ingestion_order: int | None = None,
) -> list[ChatMessage]:
query = self._db.query(ChatMessageDB).filter(ChatMessageDB.chat_id == chat_id)
if not include_temporary:
query = query.filter(ChatMessageDB.is_temporary.is_(False))
- db_models = query.order_by(desc(ChatMessageDB.sent_at)).offset(skip).limit(limit).all()
+ if cutoff_sent_at is not None and cutoff_ingestion_order is not None:
+ query = query.filter(
+ or_(
+ ChatMessageDB.sent_at < cutoff_sent_at,
+ and_(
+ ChatMessageDB.sent_at == cutoff_sent_at,
+ ChatMessageDB.ingestion_order <= cutoff_ingestion_order,
+ ),
+ ),
+ )
+ db_models = query.order_by(
+ desc(ChatMessageDB.sent_at),
+ desc(ChatMessageDB.ingestion_order),
+ ).offset(skip).limit(limit).all()
return [domain(db_model) for db_model in db_models if db_model is not None]
+ def get_by_ingestion_order(
+ self,
+ chat_id: UUID,
+ ingestion_order: int,
+ ) -> ChatMessage | None:
+ db_model = self._db.query(ChatMessageDB).filter(
+ ChatMessageDB.chat_id == chat_id,
+ ChatMessageDB.ingestion_order == ingestion_order,
+ ).first()
+ return domain(db_model)
+
def save(self, message: ChatMessage) -> ChatMessage:
existing = self._db.query(ChatMessageDB).filter(
ChatMessageDB.chat_id == message.chat_id,
diff --git a/src/features/chat/message_burst.py b/src/features/chat/message_burst.py
new file mode 100644
index 00000000..6b009172
--- /dev/null
+++ b/src/features/chat/message_burst.py
@@ -0,0 +1,21 @@
+from dataclasses import dataclass
+from datetime import datetime
+from uuid import UUID
+
+
+@dataclass(frozen = True, kw_only = True)
+class ScheduledChatMessageBurst:
+ chat_id: UUID
+ author_id: UUID
+ message_count: int
+ wait_seconds: float
+
+
+@dataclass(frozen = True, kw_only = True)
+class ClaimedChatMessageBurst:
+ chat_id: UUID
+ author_id: UUID
+ message_count: int
+ last_message_sent_at: datetime
+ last_message_ingestion_order: int
+ is_addressed: bool
diff --git a/src/features/chat/message_burst_repo.py b/src/features/chat/message_burst_repo.py
new file mode 100644
index 00000000..e05ca514
--- /dev/null
+++ b/src/features/chat/message_burst_repo.py
@@ -0,0 +1,164 @@
+from datetime import datetime, timedelta
+
+from sqlalchemy import and_, case, delete, func, or_, select, update
+from sqlalchemy.dialects.postgresql import insert
+from sqlalchemy.orm import Session
+
+from db.model.chat_message_burst import ChatMessageBurstDB
+from features.chat.message.chat_message import ChatMessage
+from features.chat.message_burst import ClaimedChatMessageBurst, ScheduledChatMessageBurst
+from util.error_codes import UNEXPECTED_ERROR
+from util.errors import InternalError
+
+
+class ChatMessageBurstRepository:
+
+ _db: Session
+
+ def __init__(self, db_session: Session):
+ self._db = db_session
+
+ def record_message(
+ self,
+ message: ChatMessage,
+ is_addressed: bool,
+ quiet_period_s: float,
+ ) -> ScheduledChatMessageBurst:
+ if message.author_id is None or message.ingestion_order is None:
+ raise InternalError("Stored burst messages require an author and ingestion order", UNEXPECTED_ERROR)
+
+ database_now = self._db.execute(select(func.now())).scalar_one().replace(tzinfo = None)
+ process_after = database_now + timedelta(seconds = quiet_period_s)
+ insert_statement = insert(ChatMessageBurstDB).values(
+ chat_id = message.chat_id,
+ author_id = message.author_id,
+ message_count = 1,
+ process_after = process_after,
+ last_message_sent_at = message.sent_at,
+ last_message_ingestion_order = message.ingestion_order,
+ is_addressed = is_addressed,
+ is_processing = False,
+ )
+ excluded = insert_statement.excluded
+ last_message_is_newer = or_(
+ excluded.last_message_sent_at > ChatMessageBurstDB.last_message_sent_at,
+ and_(
+ excluded.last_message_sent_at == ChatMessageBurstDB.last_message_sent_at,
+ excluded.last_message_ingestion_order > ChatMessageBurstDB.last_message_ingestion_order,
+ ),
+ )
+ statement = insert_statement.on_conflict_do_update(
+ index_elements = [ChatMessageBurstDB.chat_id, ChatMessageBurstDB.author_id],
+ set_ = {
+ "message_count": ChatMessageBurstDB.message_count + 1,
+ "process_after": excluded.process_after,
+ "last_message_sent_at": case(
+ (last_message_is_newer, excluded.last_message_sent_at),
+ else_ = ChatMessageBurstDB.last_message_sent_at,
+ ),
+ "last_message_ingestion_order": case(
+ (last_message_is_newer, excluded.last_message_ingestion_order),
+ else_ = ChatMessageBurstDB.last_message_ingestion_order,
+ ),
+ "is_addressed": or_(
+ ChatMessageBurstDB.is_addressed,
+ excluded.is_addressed,
+ ),
+ },
+ ).returning(
+ ChatMessageBurstDB.chat_id,
+ ChatMessageBurstDB.author_id,
+ ChatMessageBurstDB.message_count,
+ )
+ row = self._db.execute(statement).one()
+ self._db.commit()
+ return ScheduledChatMessageBurst(
+ chat_id = row.chat_id,
+ author_id = row.author_id,
+ message_count = row.message_count,
+ wait_seconds = quiet_period_s,
+ )
+
+ def claim(
+ self,
+ scheduled: ScheduledChatMessageBurst,
+ ) -> ClaimedChatMessageBurst | None:
+ database_now = self._db.execute(select(func.now())).scalar_one().replace(tzinfo = None)
+ statement = update(ChatMessageBurstDB).where(
+ ChatMessageBurstDB.chat_id == scheduled.chat_id,
+ ChatMessageBurstDB.author_id == scheduled.author_id,
+ ChatMessageBurstDB.message_count == scheduled.message_count,
+ ChatMessageBurstDB.process_after <= database_now,
+ ChatMessageBurstDB.is_processing.is_(False),
+ ).values(
+ is_processing = True,
+ ).returning(
+ ChatMessageBurstDB.message_count,
+ ChatMessageBurstDB.last_message_sent_at,
+ ChatMessageBurstDB.last_message_ingestion_order,
+ ChatMessageBurstDB.is_addressed,
+ )
+ row = self._db.execute(statement).one_or_none()
+ self._db.commit()
+ if row is None:
+ return None
+ return ClaimedChatMessageBurst(
+ chat_id = scheduled.chat_id,
+ author_id = scheduled.author_id,
+ message_count = row.message_count,
+ last_message_sent_at = row.last_message_sent_at,
+ last_message_ingestion_order = row.last_message_ingestion_order,
+ is_addressed = row.is_addressed,
+ )
+
+ def finalize(
+ self,
+ claimed: ClaimedChatMessageBurst,
+ ) -> ScheduledChatMessageBurst | None:
+ database_now = self._db.execute(select(func.now())).scalar_one().replace(tzinfo = None)
+ completed = self._db.execute(
+ delete(ChatMessageBurstDB).where(
+ ChatMessageBurstDB.chat_id == claimed.chat_id,
+ ChatMessageBurstDB.author_id == claimed.author_id,
+ ChatMessageBurstDB.message_count == claimed.message_count,
+ ChatMessageBurstDB.is_processing.is_(True),
+ ).returning(ChatMessageBurstDB.message_count),
+ ).one_or_none()
+
+ if completed is not None:
+ self._db.commit()
+ return None
+
+ queued = self._db.execute(
+ update(ChatMessageBurstDB).where(
+ ChatMessageBurstDB.chat_id == claimed.chat_id,
+ ChatMessageBurstDB.author_id == claimed.author_id,
+ ChatMessageBurstDB.is_processing.is_(True),
+ ).values(
+ is_processing = False,
+ ).returning(
+ ChatMessageBurstDB.message_count,
+ ChatMessageBurstDB.process_after,
+ ),
+ ).one_or_none()
+
+ self._db.commit()
+ if queued is None:
+ return None
+
+ return ScheduledChatMessageBurst(
+ chat_id = claimed.chat_id,
+ author_id = claimed.author_id,
+ message_count = queued.message_count,
+ wait_seconds = max(
+ 0.0,
+ (queued.process_after - database_now).total_seconds(),
+ ),
+ )
+
+ def delete_older_than(self, cutoff: datetime) -> int:
+ deleted_count = self._db.query(ChatMessageBurstDB).filter(
+ ChatMessageBurstDB.process_after < cutoff,
+ ).delete(synchronize_session = False)
+ self._db.commit()
+ return deleted_count
diff --git a/src/features/chat/message_burst_service.py b/src/features/chat/message_burst_service.py
new file mode 100644
index 00000000..515eac58
--- /dev/null
+++ b/src/features/chat/message_burst_service.py
@@ -0,0 +1,257 @@
+import asyncio
+from datetime import datetime
+from time import sleep
+
+from langchain_core.messages import AIMessage
+
+from db.model.chat_config import ChatConfigDB
+from db.sql import get_detached_session
+from di.di import DI
+from features.chat.chat_agent import ChatAgent
+from features.chat.config.chat_config import ChatConfig
+from features.chat.ingested_chat_message import IngestedChatMessage
+from features.chat.message.chat_message import ChatMessage
+from features.chat.message_burst import ClaimedChatMessageBurst, ScheduledChatMessageBurst
+from features.external_tools.intelligence_presets import default_tool_for
+from features.integrations import prompt_resolvers
+from features.integrations.integrations import (
+ format_reaction_response,
+ is_reaction_response,
+ resolve_agent_user,
+ resolve_external_handle,
+)
+from util import log
+from util.config import config
+from util.error_codes import UNSUPPORTED_CHAT_TYPE
+from util.errors import ConfigurationError, ServiceError
+from util.functions import parse_ai_message_content, silent
+
+
+class MessageBurstService:
+
+ __di: DI
+
+ def __init__(self, di: DI):
+ self.__di = di
+
+ def is_explicitly_addressed(
+ self,
+ message_text: str,
+ chat_type: ChatConfigDB.ChatType,
+ ) -> bool:
+ agent = resolve_agent_user(chat_type)
+ agent_handle = resolve_external_handle(agent, chat_type)
+ if not agent_handle:
+ return False
+ non_quoted_text = "\n".join(
+ line
+ for line in message_text.splitlines()
+ if not line.lstrip().startswith(">>")
+ )
+ return f"@{agent_handle}" in non_quoted_text
+
+ def record(
+ self,
+ message: ChatMessage,
+ is_addressed: bool,
+ ) -> ScheduledChatMessageBurst:
+ return self.__di.chat_message_burst_repo.record_message(
+ message = message,
+ is_addressed = is_addressed,
+ quiet_period_s = config.chat_burst_quiet_period_s,
+ )
+
+ def load_claimed_message(
+ self,
+ claim: ClaimedChatMessageBurst,
+ ) -> IngestedChatMessage | None:
+ chat = self.__di.chat_config_repo.get(claim.chat_id)
+ author = self.__di.user_repo.get(claim.author_id)
+ message = self.__di.chat_message_repo.get_by_ingestion_order(
+ claim.chat_id,
+ claim.last_message_ingestion_order,
+ )
+
+ if chat is None or author is None or message is None:
+ log.w(
+ f"Could not load claimed burst with {claim.message_count} messages "
+ f"for chat {claim.chat_id.hex}",
+ )
+ return None
+
+ attachments = self.__di.chat_attachment_repo.get_all_by_message(
+ message.chat_id,
+ message.message_id,
+ )
+ return IngestedChatMessage(
+ chat = chat,
+ author = author,
+ message = message,
+ attachments = attachments,
+ raw_message_text = message.text,
+ )
+
+ def process_message(
+ self,
+ resolved_domain_data: IngestedChatMessage,
+ claim: ClaimedChatMessageBurst | None = None,
+ command_only: bool = False,
+ ) -> bool:
+ should_notify_of_errors = False
+ try:
+ # build the agent against the claimed history cutoff
+ tool = self.__di.tool_choice_resolver.get_tool(
+ ChatAgent.TOOL_TYPE,
+ default_tool_for(ChatAgent.TOOL_TYPE),
+ )
+ chat_agent = self.__di.chat_agent(
+ trigger_message_text = resolved_domain_data.raw_message_text,
+ trigger_message_id = resolved_domain_data.message.message_id,
+ configured_tool = tool,
+ cutoff_sent_at = claim.last_message_sent_at if claim else resolved_domain_data.message.sent_at,
+ cutoff_ingestion_order = (
+ claim.last_message_ingestion_order
+ if claim
+ else resolved_domain_data.message.ingestion_order
+ ),
+ explicitly_addressed = claim.is_addressed if claim else True,
+ )
+
+ if command_only:
+ command_result = chat_agent.process_commands()
+ if not command_result.is_handled or command_result.reply is None:
+ return False
+ answer = command_result.reply
+ else:
+ answer = chat_agent.execute()
+
+ if not answer or not answer.content:
+ log.d("No LLM response needed (command handled or no reply required)")
+ return False
+
+ self.__di.rollback_db_session()
+
+ # deliver the agent response through the current chat platform
+ sent_messages: int = 0
+ agent = resolve_agent_user(resolved_domain_data.chat.chat_type)
+ as_reaction = parse_ai_message_content(answer)
+ if is_reaction_response(as_reaction, resolved_domain_data.chat.chat_type):
+ self.__di.chat_message_repo.save(
+ ChatMessage(
+ chat_id = resolved_domain_data.chat.chat_id,
+ message_id = f"reaction:{resolved_domain_data.message.message_id}",
+ author_id = agent.id,
+ sent_at = datetime.now(),
+ text = format_reaction_response(as_reaction),
+ ),
+ )
+ self.__di.rollback_db_session()
+ silent(self.__di.platform_bot_sdk().set_reaction)(
+ str(resolved_domain_data.chat.external_id),
+ resolved_domain_data.message.message_id,
+ as_reaction,
+ )
+ log.i(f"Reacted to message {resolved_domain_data.message.message_id} with {as_reaction}")
+ else:
+ domain_messages = self.__di.domain_langchain_mapper.map_bot_message_to_storage(
+ resolved_domain_data.chat,
+ answer,
+ )
+ for message in domain_messages:
+ should_notify_of_errors = True
+ self.__send_text_message(resolved_domain_data.chat, message.text)
+ self.__di.rollback_db_session()
+ sleep(0.1)
+ sent_messages += 1
+
+ if resolved_domain_data.chat.chat_type == ChatConfigDB.ChatType.whatsapp:
+ self.__di.whatsapp_bot_sdk.mark_as_read(resolved_domain_data.message.message_id)
+
+ log.t(f"Finished responding to updates. \n[{agent.full_name}]: {answer.content}")
+ log.i(f"Sent {sent_messages} messages")
+ return True
+ except Exception as e:
+ log.e("Failed to process message", e)
+ if should_notify_of_errors:
+ self.__di.rollback_db_session()
+ self.__notify_of_errors(resolved_domain_data, e)
+ return False
+
+ async def process_after_quiet_period(
+ self,
+ scheduled: ScheduledChatMessageBurst,
+ ) -> bool:
+ processed = False
+ while True:
+ # wait without retaining a database session
+ await asyncio.sleep(scheduled.wait_seconds)
+
+ claimed = await asyncio.to_thread(self.__claim, scheduled)
+ if claimed is None:
+ return processed
+
+ try:
+ processed = await asyncio.to_thread(self.__process_claimed_burst, claimed) or processed
+ finally:
+ scheduled = await asyncio.to_thread(self.__finalize, claimed)
+ if scheduled is None:
+ return processed
+
+ def __process_claimed_burst(self, claim: ClaimedChatMessageBurst) -> bool:
+ try:
+ with get_detached_session() as db:
+ di = self.__di.clone(db)
+ service = di.message_burst_service
+
+ resolved_domain_data = service.load_claimed_message(claim)
+ if resolved_domain_data is None:
+ return False
+
+ di.inject_invoker(resolved_domain_data.author)
+ di.inject_invoker_chat(resolved_domain_data.chat)
+ return service.process_message(resolved_domain_data, claim = claim)
+ except Exception as e:
+ log.e(f"Failed to process burst with {claim.message_count} messages", e)
+ return False
+
+ def __send_text_message(
+ self,
+ chat: ChatConfig,
+ text: str,
+ ) -> None:
+ match chat.chat_type:
+ case ChatConfigDB.ChatType.telegram:
+ self.__di.telegram_bot_sdk.send_text_message(chat, text)
+ case ChatConfigDB.ChatType.whatsapp:
+ self.__di.whatsapp_bot_sdk.send_text_message(chat, text)
+ case _:
+ raise ConfigurationError(
+ f"Unsupported chat type: {chat.chat_type}",
+ UNSUPPORTED_CHAT_TYPE,
+ )
+
+ @silent
+ def __notify_of_errors(
+ self,
+ resolved_domain_data: IngestedChatMessage,
+ error: Exception,
+ ) -> None:
+ emoji = error.emoji if isinstance(error, ServiceError) else "๐คฏ"
+ answer = AIMessage(prompt_resolvers.simple_chat_error(str(error), emoji = emoji))
+ messages = self.__di.domain_langchain_mapper.map_bot_message_to_storage(
+ chat = resolved_domain_data.chat,
+ message = answer,
+ )
+ for message in messages:
+ self.__send_text_message(resolved_domain_data.chat, message.text)
+ self.__di.rollback_db_session()
+ sleep(0.1)
+ log.t("Replied with the error")
+
+ def __claim(self, scheduled: ScheduledChatMessageBurst) -> ClaimedChatMessageBurst | None:
+ with get_detached_session() as db:
+ return self.__di.clone(db).chat_message_burst_repo.claim(scheduled)
+
+ def __finalize(self, claimed: ClaimedChatMessageBurst) -> ScheduledChatMessageBurst | None:
+ with get_detached_session() as db:
+ return self.__di.clone(db).chat_message_burst_repo.finalize(claimed)
diff --git a/src/features/chat/telegram/telegram_update_responder.py b/src/features/chat/telegram/telegram_update_responder.py
index 6df88435..5f6d379b 100644
--- a/src/features/chat/telegram/telegram_update_responder.py
+++ b/src/features/chat/telegram/telegram_update_responder.py
@@ -1,110 +1,83 @@
-from datetime import datetime
-from time import sleep
-
-from langchain_core.messages import AIMessage
+import asyncio
+from dataclasses import dataclass
from db.sql import get_detached_session
from di.di import DI
-from features.chat.chat_agent import ChatAgent
-from features.chat.ingested_chat_message import IngestedChatMessage
-from features.chat.message.chat_message import ChatMessage
+from features.chat.command_processor import is_known_command
+from features.chat.message_burst import ScheduledChatMessageBurst
from features.chat.telegram.model.update import Update
-from features.external_tools.intelligence_presets import default_tool_for
-from features.integrations import prompt_resolvers
-from features.integrations.integrations import format_reaction_response, is_reaction_response, resolve_agent_user
+from features.integrations.integrations import resolve_agent_user, resolve_external_handle
from util import log
from util.config import config
-from util.errors import ServiceError
-from util.functions import parse_ai_message_content, silent
-def respond_to_update(update: Update) -> bool:
+@dataclass(frozen = True, kw_only = True)
+class _IngressOutcome:
+ scheduled_burst: ScheduledChatMessageBurst | None = None
+ processed: bool = False
+
+
+async def respond_to_update(update: Update) -> bool:
+ outcome = await asyncio.to_thread(_ingest_update, update)
+ if outcome.scheduled_burst is None:
+ return outcome.processed
+ return await _process_scheduled_burst(outcome.scheduled_burst)
+
+
+async def _process_scheduled_burst(
+ scheduled: ScheduledChatMessageBurst,
+) -> bool:
+ di = DI(
+ invoker_id = scheduled.author_id.hex,
+ invoker_chat_id = scheduled.chat_id.hex,
+ )
+ return await di.message_burst_service.process_after_quiet_period(
+ scheduled = scheduled,
+ )
+
+
+def _ingest_update(update: Update) -> _IngressOutcome:
if config.log_telegram_update:
log.t(f"Received a Telegram update: `{update}`")
with get_detached_session() as db:
di = DI(db)
-
- resolved_domain_data: IngestedChatMessage | None = None
- should_notify_of_errors = False
try:
# store and map to domain models (throws in case of error)
resolved_domain_data = di.telegram_chat_inbound_service.ingest_update(update)
if not resolved_domain_data:
log.d("No Telegram response needed (update ignored)")
- return False
- if not resolved_domain_data.author:
+ return _IngressOutcome()
+ # filter out messages without authors
+ if not resolved_domain_data.author or resolved_domain_data.author.id is None:
log.d("Not responding to messages without author")
- return False
+ return _IngressOutcome()
+
di.inject_invoker(resolved_domain_data.author)
di.inject_invoker_chat(resolved_domain_data.chat)
-
- # process the update using LLM; get instead of require to allow the first message to be sent
- tool = di.tool_choice_resolver.get_tool(ChatAgent.TOOL_TYPE, default_tool_for(ChatAgent.TOOL_TYPE))
- chat_agent = di.chat_agent(
- trigger_message_text = resolved_domain_data.raw_message_text,
- trigger_message_id = resolved_domain_data.message.message_id,
- trigger_message_sent_at = resolved_domain_data.message.sent_at,
- configured_tool = tool,
+ agent_handle = resolve_external_handle(
+ resolve_agent_user(resolved_domain_data.chat.chat_type),
+ resolved_domain_data.chat.chat_type,
)
- answer = chat_agent.execute()
- if not answer or not answer.content:
- log.d("No LLM response needed (command handled or no reply required)")
- return False
- di.rollback_db_session()
-
- # send and store the response[s]
- sent_messages: int = 0
- agent = resolve_agent_user(resolved_domain_data.chat.chat_type)
- as_reaction = parse_ai_message_content(answer)
- if is_reaction_response(as_reaction, resolved_domain_data.chat.chat_type):
- di.chat_message_repo.save(
- ChatMessage(
- chat_id = resolved_domain_data.chat.chat_id,
- message_id = f"reaction:{resolved_domain_data.message.message_id}",
- author_id = agent.id,
- sent_at = datetime.now(),
- text = format_reaction_response(as_reaction),
+ if is_known_command(resolved_domain_data.raw_message_text, agent_handle):
+ return _IngressOutcome(
+ processed = di.message_burst_service.process_message(
+ resolved_domain_data,
+ command_only = True,
),
)
- di.rollback_db_session()
- silent(di.platform_bot_sdk().set_reaction)(
- str(resolved_domain_data.chat.external_id),
- resolved_domain_data.message.message_id,
- as_reaction,
- )
- log.i(f"Reacted to message {resolved_domain_data.message.message_id} with {as_reaction}")
- else:
- domain_messages = di.domain_langchain_mapper.map_bot_message_to_storage(resolved_domain_data.chat, answer)
- for message in domain_messages:
- should_notify_of_errors = True
- di.telegram_bot_sdk.send_text_message(resolved_domain_data.chat, message.text)
- di.rollback_db_session()
- sleep(0.1)
- sent_messages += 1
- log.t(f"Finished responding to updates. \n[{agent.full_name}]: {answer.content}")
- log.i(f"Sent {sent_messages} messages")
- return True
+ scheduled_burst = di.message_burst_service.record(
+ message = resolved_domain_data.message,
+ is_addressed = (
+ resolved_domain_data.chat.is_private
+ or di.message_burst_service.is_explicitly_addressed(
+ resolved_domain_data.raw_message_text,
+ resolved_domain_data.chat.chat_type,
+ )
+ ),
+ )
+ return _IngressOutcome(scheduled_burst = scheduled_burst)
except Exception as e:
log.e(f"Failed to ingest: {update}", e)
- if should_notify_of_errors and resolved_domain_data:
- di.rollback_db_session()
- __notify_of_errors(di, resolved_domain_data, e)
- return False
-
-
-@silent
-def __notify_of_errors(
- di: DI,
- resolved_domain_data: IngestedChatMessage,
- error: Exception,
-):
- emoji = error.emoji if isinstance(error, ServiceError) else "๐คฏ"
- answer = AIMessage(prompt_resolvers.simple_chat_error(str(error), emoji = emoji))
- messages = di.domain_langchain_mapper.map_bot_message_to_storage(resolved_domain_data.chat, answer)
- for message in messages:
- di.telegram_bot_sdk.send_text_message(resolved_domain_data.chat, message.text)
- di.rollback_db_session()
- sleep(0.1)
- log.t("Replied with the error")
+ return _IngressOutcome()
diff --git a/src/features/chat/whatsapp/whatsapp_update_responder.py b/src/features/chat/whatsapp/whatsapp_update_responder.py
index a9ee09ab..52e83aae 100644
--- a/src/features/chat/whatsapp/whatsapp_update_responder.py
+++ b/src/features/chat/whatsapp/whatsapp_update_responder.py
@@ -1,119 +1,96 @@
-from datetime import datetime
-from time import sleep
-
-from langchain_core.messages import AIMessage
+import asyncio
+from dataclasses import dataclass, field
from db.sql import get_detached_session
from di.di import DI
-from features.chat.chat_agent import ChatAgent
-from features.chat.ingested_chat_message import IngestedChatMessage
-from features.chat.message.chat_message import ChatMessage
+from features.chat.command_processor import is_known_command
+from features.chat.message_burst import ScheduledChatMessageBurst
from features.chat.whatsapp.model.update import Update
-from features.external_tools.intelligence_presets import default_tool_for
-from features.integrations import prompt_resolvers
-from features.integrations.integrations import format_reaction_response, is_reaction_response, resolve_agent_user
+from features.integrations.integrations import resolve_agent_user, resolve_external_handle
from util import log
from util.config import config
-from util.errors import ServiceError
-from util.functions import parse_ai_message_content, silent
-def respond_to_update(update: Update) -> bool:
+@dataclass(frozen = True, kw_only = True)
+class _IngressOutcome:
+ scheduled_bursts: list[ScheduledChatMessageBurst] = field(default_factory = list)
+ processed: bool = False
+
+
+async def respond_to_update(update: Update) -> bool:
+ outcome = await asyncio.to_thread(_ingest_update, update)
+ if not outcome.scheduled_bursts:
+ return outcome.processed
+ results = await asyncio.gather(*(
+ _process_scheduled_burst(scheduled)
+ for scheduled in outcome.scheduled_bursts
+ ))
+ return outcome.processed or any(results)
+
+
+async def _process_scheduled_burst(
+ scheduled: ScheduledChatMessageBurst,
+) -> bool:
+ di = DI(
+ invoker_id = scheduled.author_id.hex,
+ invoker_chat_id = scheduled.chat_id.hex,
+ )
+ return await di.message_burst_service.process_after_quiet_period(
+ scheduled = scheduled,
+ )
+
+
+def _ingest_update(update: Update) -> _IngressOutcome:
if config.log_whatsapp_update:
log.t(f"Received a WhatsApp update: `{update}`")
with get_detached_session() as db:
di = DI(db)
-
- resolved_domain_data_all: list[IngestedChatMessage] = []
- resolved_domain_data: IngestedChatMessage | None = None
- should_notify_of_errors = False
try:
# store and map to domain models (throws in case of error)
resolved_domain_data_all = di.whatsapp_chat_inbound_service.ingest_update(update)
if not resolved_domain_data_all:
log.w("No messages to process in this WhatsApp update (likely a status update or notification)")
- return False
-
- # filter out messages without authors
- resolved_domain_data_all = [message for message in resolved_domain_data_all if message.author]
- if not resolved_domain_data_all:
- log.d("Not responding to messages without authors")
- return False
- # we inject DI context for the latest message only, so let's sort by timestamp
- resolved_domain_data = max(resolved_domain_data_all, key = lambda r: r.message.sent_at)
- di.inject_invoker(resolved_domain_data.author)
- di.inject_invoker_chat(resolved_domain_data.chat)
+ return _IngressOutcome()
- # process the update using LLM; get instead of require to allow the first message to be sent
- tool = di.tool_choice_resolver.get_tool(ChatAgent.TOOL_TYPE, default_tool_for(ChatAgent.TOOL_TYPE))
- chat_agent = di.chat_agent(
- trigger_message_text = resolved_domain_data.raw_message_text,
- trigger_message_id = resolved_domain_data.message.message_id,
- trigger_message_sent_at = resolved_domain_data.message.sent_at,
- configured_tool = tool,
- )
- answer = chat_agent.execute()
- if not answer or not answer.content:
- log.d("No LLM response needed (command handled or no reply required)")
- return False
- di.rollback_db_session()
+ scheduled_bursts: list[ScheduledChatMessageBurst] = []
+ processed = False
+ for resolved_domain_data in resolved_domain_data_all:
+ # filter out messages without authors
+ if not resolved_domain_data.author or resolved_domain_data.author.id is None:
+ log.d("Not responding to WhatsApp message without author")
+ continue
- # send and store the response[s]
- sent_messages: int = 0
- agent = resolve_agent_user(resolved_domain_data.chat.chat_type)
- as_reaction = parse_ai_message_content(answer)
- if is_reaction_response(as_reaction, resolved_domain_data.chat.chat_type):
- di.chat_message_repo.save(
- ChatMessage(
- chat_id = resolved_domain_data.chat.chat_id,
- message_id = f"reaction:{resolved_domain_data.message.message_id}",
- author_id = agent.id,
- sent_at = datetime.now(),
- text = format_reaction_response(as_reaction),
- ),
+ di.inject_invoker(resolved_domain_data.author)
+ di.inject_invoker_chat(resolved_domain_data.chat)
+ agent_handle = resolve_external_handle(
+ resolve_agent_user(resolved_domain_data.chat.chat_type),
+ resolved_domain_data.chat.chat_type,
)
- di.rollback_db_session()
- silent(di.platform_bot_sdk().set_reaction)(
- str(resolved_domain_data.chat.external_id),
- resolved_domain_data.message.message_id,
- as_reaction,
- )
- log.i(f"Reacted to message {resolved_domain_data.message.message_id} with {as_reaction}")
- else:
- domain_messages = di.domain_langchain_mapper.map_bot_message_to_storage(resolved_domain_data.chat, answer)
- for message in domain_messages:
- should_notify_of_errors = True
- di.whatsapp_bot_sdk.send_text_message(resolved_domain_data.chat, message.text)
- di.rollback_db_session()
- sleep(0.1)
- sent_messages += 1
-
- # mark the incoming message as read
- di.whatsapp_bot_sdk.mark_as_read(resolved_domain_data.message.message_id)
+ if is_known_command(resolved_domain_data.raw_message_text, agent_handle):
+ processed = (
+ di.message_burst_service.process_message(
+ resolved_domain_data,
+ command_only = True,
+ )
+ or processed
+ )
+ continue
- log.t(f"Finished responding to updates. \n[{agent.full_name}]: {answer.content}")
- log.i(f"Sent {sent_messages} messages")
- return True
+ scheduled_bursts.append(di.message_burst_service.record(
+ message = resolved_domain_data.message,
+ is_addressed = (
+ resolved_domain_data.chat.is_private
+ or di.message_burst_service.is_explicitly_addressed(
+ resolved_domain_data.raw_message_text,
+ resolved_domain_data.chat.chat_type,
+ )
+ ),
+ ))
+ return _IngressOutcome(
+ scheduled_bursts = scheduled_bursts,
+ processed = processed,
+ )
except Exception as e:
log.e(f"Failed to ingest: {update}", e)
- if should_notify_of_errors and resolved_domain_data:
- di.rollback_db_session()
- __notify_of_errors(di, resolved_domain_data, e)
- return False
-
-
-@silent
-def __notify_of_errors(
- di: DI,
- resolved_domain_data: IngestedChatMessage,
- error: Exception,
-):
- emoji = error.emoji if isinstance(error, ServiceError) else "๐คฏ"
- answer = AIMessage(prompt_resolvers.simple_chat_error(str(error), emoji = emoji))
- messages = di.domain_langchain_mapper.map_bot_message_to_storage(resolved_domain_data.chat, answer)
- for message in messages:
- di.whatsapp_bot_sdk.send_text_message(resolved_domain_data.chat, message.text)
- di.rollback_db_session()
- sleep(0.1)
- log.t("Replied with the error")
+ return _IngressOutcome()
diff --git a/src/features/cleanup/cleanup_service.py b/src/features/cleanup/cleanup_service.py
index d6e472ef..7cc192e9 100644
--- a/src/features/cleanup/cleanup_service.py
+++ b/src/features/cleanup/cleanup_service.py
@@ -10,6 +10,7 @@ class CleanupResult:
attachments_deleted: int = field(default = 0)
orphaned_attachments_deleted: int = field(default = 0)
messages_deleted: int = field(default = 0)
+ message_bursts_deleted: int = field(default = 0)
cache_entries_cleared: int = field(default = 0)
usage_records_deleted: int = field(default = 0)
price_alerts_deleted: int = field(default = 0)
@@ -36,6 +37,12 @@ def run(self) -> CleanupResult:
except Exception as e:
log.e(f" Cleanup phase 1 (messages / attachments) failed: {e}")
+ try:
+ result.message_bursts_deleted = self.__di.chat_message_burst_repo.delete_older_than(message_cutoff)
+ log.i(f" Cleanup phase 1D: deleted {result.message_bursts_deleted} message bursts")
+ except Exception as e:
+ log.e(f" Cleanup phase 1D (message bursts) failed: {e}")
+
try:
result.cache_entries_cleared = self.__di.tools_cache_repo.delete_expired()
log.i(f" Cleanup phase 2: cleared {result.cache_entries_cleared} cache entries")
diff --git a/src/main.py b/src/main.py
index 2a72bd34..190322fd 100644
--- a/src/main.py
+++ b/src/main.py
@@ -78,6 +78,7 @@ async def lifespan(owner: FastAPI):
app = FastAPI(
docs_url = None,
redoc_url = None,
+ openapi_url = None,
title = "The Agent's API",
description = "This is the API service for The Agent.",
debug = config.log_level in ["local", "trace", "debug"],
diff --git a/src/util/config.py b/src/util/config.py
index 15b14169..b917fad6 100644
--- a/src/util/config.py
+++ b/src/util/config.py
@@ -55,7 +55,7 @@ class Config(metaclass = Singleton):
whatsapp_phone_number_id: str
whatsapp_bot_phone_number: str
chat_history_depth: int
- chat_debounce_delay_s: float
+ chat_burst_quiet_period_s: float
cleanup_message_retention_days: int
cleanup_price_alert_staleness_days: int
cleanup_sponsorship_staleness_days: int
@@ -170,7 +170,7 @@ def __init__(
def_whatsapp_phone_number_id: str = "invalid",
def_whatsapp_bot_phone_number: str = "11234567890",
def_chat_history_depth: int = 30,
- def_chat_debounce_delay_s: float = 1.0,
+ def_chat_burst_quiet_period_s: float = 1.0,
def_cleanup_message_retention_days: int = 30,
def_cleanup_price_alert_staleness_days: int = 360,
def_cleanup_sponsorship_staleness_days: int = 30,
@@ -254,7 +254,7 @@ def __init__(
self.whatsapp_phone_number_id = self.__env("WHATSAPP_PHONE_NUMBER_ID", lambda: def_whatsapp_phone_number_id)
self.whatsapp_bot_phone_number = self.__env("WHATSAPP_BOT_PHONE_NUMBER", lambda: def_whatsapp_bot_phone_number)
self.chat_history_depth = int(self.__env("CHAT_HISTORY_DEPTH", lambda: str(def_chat_history_depth)))
- self.chat_debounce_delay_s = float(self.__env("CHAT_DEBOUNCE_DELAY_S", lambda: str(def_chat_debounce_delay_s)))
+ self.chat_burst_quiet_period_s = float(self.__env("CHAT_BURST_QUIET_PERIOD_S", lambda: str(def_chat_burst_quiet_period_s)))
self.cleanup_message_retention_days = int(self.__env("CLEANUP_MESSAGE_RETENTION_DAYS", lambda: str(def_cleanup_message_retention_days)))
self.cleanup_price_alert_staleness_days = int(self.__env("CLEANUP_PRICE_ALERT_STALENESS_DAYS", lambda: str(def_cleanup_price_alert_staleness_days)))
self.cleanup_sponsorship_staleness_days = int(self.__env("CLEANUP_SPONSORSHIP_STALENESS_DAYS", lambda: str(def_cleanup_sponsorship_staleness_days)))
diff --git a/test/db/sql_util.py b/test/db/sql_util.py
index c456051f..0567a165 100644
--- a/test/db/sql_util.py
+++ b/test/db/sql_util.py
@@ -7,6 +7,7 @@
from features.chat.config.chat_config_repo import ChatConfigRepository
from features.chat.membership.chat_membership_repo import ChatMembershipRepository
from features.chat.message.chat_message_repo import ChatMessageRepository
+from features.chat.message_burst_repo import ChatMessageBurstRepository
from features.currencies.price_alert_repo import PriceAlertRepository
from features.sponsorships.sponsorship_repo import SponsorshipRepository
from features.tools_cache.tools_cache_repo import ToolsCacheRepository
@@ -57,6 +58,11 @@ def chat_message_repo(self) -> ChatMessageRepository:
self.start_session()
return ChatMessageRepository(self.__session)
+ def chat_message_burst_repo(self) -> ChatMessageBurstRepository:
+ if not self.__is_session_active:
+ self.start_session()
+ return ChatMessageBurstRepository(self.__session)
+
def chat_attachment_repo(self) -> ChatAttachmentRepository:
if not self.__is_session_active:
self.start_session()
diff --git a/test/features/chat/attachment/test_chat_attachment_repo.py b/test/features/chat/attachment/test_chat_attachment_repo.py
index 60ee4165..17b8a38e 100644
--- a/test/features/chat/attachment/test_chat_attachment_repo.py
+++ b/test/features/chat/attachment/test_chat_attachment_repo.py
@@ -1,6 +1,7 @@
import unittest
from dataclasses import replace
from datetime import datetime, timedelta
+from itertools import count
from uuid import UUID
from db.sql_util import SQLUtil
@@ -22,6 +23,7 @@ def setUp(self):
self.sql = SQLUtil()
self.repo = self.sql.chat_attachment_repo()
self.uploader = self.sql.user_repo().save(User(full_name = "Uploader"))
+ self.message_order = count(1)
def tearDown(self):
self.sql.end_session()
@@ -41,6 +43,7 @@ def _create_message(
self.sql.chat_message_repo().save(ChatMessage(
chat_id = chat_id,
message_id = message_id,
+ ingestion_order = next(self.message_order),
sent_at = sent_at or datetime.now(),
text = message_id,
))
diff --git a/test/features/chat/message/test_chat_message_mapper.py b/test/features/chat/message/test_chat_message_mapper.py
index f4ba7228..198d04e1 100644
--- a/test/features/chat/message/test_chat_message_mapper.py
+++ b/test/features/chat/message/test_chat_message_mapper.py
@@ -23,6 +23,7 @@ def setUp(self):
self.db_model = ChatMessageDB(
chat_id = self.chat_id,
message_id = "message1",
+ ingestion_order = 7,
author_id = self.author_id,
sent_at = self.sent_at,
text = "Hello",
@@ -31,6 +32,7 @@ def setUp(self):
self.domain_model = ChatMessage(
chat_id = self.chat_id,
message_id = "message1",
+ ingestion_order = 7,
author_id = self.author_id,
sent_at = self.sent_at,
text = "Hello",
@@ -54,6 +56,7 @@ def test_db_maps_all_fields(self):
self.assertIsNotNone(result)
self.assertEqual(result.chat_id, self.domain_model.chat_id)
self.assertEqual(result.message_id, self.domain_model.message_id)
+ self.assertEqual(result.ingestion_order, self.domain_model.ingestion_order)
self.assertEqual(result.author_id, self.domain_model.author_id)
self.assertEqual(result.sent_at, self.domain_model.sent_at)
self.assertEqual(result.text, self.domain_model.text)
@@ -78,6 +81,7 @@ def test_apply_to_db_model_updates_mutable_fields_and_preserves_identity(self):
self.assertEqual(self.db_model.chat_id, self.chat_id)
self.assertEqual(self.db_model.message_id, "message1")
+ self.assertEqual(self.db_model.ingestion_order, self.domain_model.ingestion_order)
self.assertIsNone(self.db_model.author_id)
self.assertEqual(self.db_model.sent_at, domain_model.sent_at)
self.assertEqual(self.db_model.text, domain_model.text)
@@ -94,6 +98,7 @@ def test_from_remote_data_creates_complete_domain_state(self):
self.assertEqual(result.chat_id, self.chat_id)
self.assertEqual(result.message_id, remote_data.message_id)
+ self.assertIsNone(result.ingestion_order)
self.assertEqual(result.author_id, self.author_id)
self.assertEqual(result.sent_at, remote_data.sent_at)
self.assertEqual(result.text, remote_data.text)
@@ -111,6 +116,7 @@ def test_apply_remote_data_preserves_identity_and_applies_resolved_author(self):
self.assertEqual(result.chat_id, self.domain_model.chat_id)
self.assertEqual(result.message_id, self.domain_model.message_id)
+ self.assertEqual(result.ingestion_order, self.domain_model.ingestion_order)
self.assertEqual(result.author_id, new_author_id)
self.assertEqual(result.sent_at, remote_data.sent_at)
self.assertEqual(result.text, remote_data.text)
@@ -126,5 +132,6 @@ def test_apply_remote_data_preserves_existing_author_when_unresolved(self):
result = apply_remote_data(self.domain_model, remote_data, None)
self.assertEqual(result.author_id, self.domain_model.author_id)
+ self.assertEqual(result.ingestion_order, self.domain_model.ingestion_order)
self.assertEqual(result.sent_at, remote_data.sent_at)
self.assertEqual(result.text, remote_data.text)
diff --git a/test/features/chat/message/test_chat_message_repo.py b/test/features/chat/message/test_chat_message_repo.py
index d838045a..ef8cd356 100644
--- a/test/features/chat/message/test_chat_message_repo.py
+++ b/test/features/chat/message/test_chat_message_repo.py
@@ -1,16 +1,39 @@
import unittest
from dataclasses import replace
from datetime import datetime, timedelta
+from itertools import count
from db.sql_util import SQLUtil
+from sqlalchemy import Connection, event
from db.model.chat_config import ChatConfigDB
+from db.model.chat_message import ChatMessageDB
from db.model.user import UserDB
from features.chat.config.chat_config import ChatConfig
from features.chat.message.chat_message import ChatMessage
from features.chat.message.chat_message_repo import ChatMessageRepository
from features.users.user import User
+_ingestion_order = count(1)
+
+
+def _assign_sqlite_ingestion_order(
+ _mapper,
+ connection: Connection,
+ target: ChatMessageDB,
+) -> None:
+ if connection.dialect.name != "sqlite" or target.ingestion_order is not None:
+ return
+ target.ingestion_order = next(_ingestion_order)
+
+
+def setUpModule() -> None:
+ event.listen(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
+
+def tearDownModule() -> None:
+ event.remove(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
class ChatMessageRepositoryTest(unittest.TestCase):
@@ -50,7 +73,8 @@ def test_save_inserts_complete_message(self):
result = self.repo.save(message)
- self.assertEqual(result, message)
+ self.assertEqual(result, replace(message, ingestion_order = result.ingestion_order))
+ self.assertIsNotNone(result.ingestion_order)
def test_get_uses_composite_identity(self):
first_chat = self._create_chat("chat1")
@@ -267,3 +291,30 @@ def test_delete_older_than_uses_strict_cutoff(self):
self.assertIsNone(self.repo.get(chat.chat_id, "old"))
self.assertIsNotNone(self.repo.get(chat.chat_id, "boundary"))
self.assertIsNotNone(self.repo.get(chat.chat_id, "new"))
+
+ def test_equal_timestamps_use_ingestion_order_for_history_cutoff(self):
+ chat = self._create_chat("chat1")
+ sent_at = datetime(2026, 1, 2, 12, 0, 0)
+ first = self.repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "first",
+ sent_at = sent_at,
+ text = "First",
+ ))
+ second = self.repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "second",
+ sent_at = sent_at,
+ text = "Second",
+ ))
+
+ latest = self.repo.get_latest_by_chat(chat.chat_id)
+ through_first = self.repo.get_latest_by_chat(
+ chat.chat_id,
+ cutoff_sent_at = first.sent_at,
+ cutoff_ingestion_order = first.ingestion_order,
+ )
+
+ self.assertEqual([message.message_id for message in latest], ["second", "first"])
+ self.assertEqual([message.message_id for message in through_first], ["first"])
+ self.assertGreater(second.ingestion_order, first.ingestion_order)
diff --git a/test/features/chat/telegram/test_telegram_chat_inbound_service.py b/test/features/chat/telegram/test_telegram_chat_inbound_service.py
index 9761384d..f4f0a9f4 100644
--- a/test/features/chat/telegram/test_telegram_chat_inbound_service.py
+++ b/test/features/chat/telegram/test_telegram_chat_inbound_service.py
@@ -1,11 +1,14 @@
import unittest
from datetime import datetime, timedelta
+from itertools import count
from unittest.mock import MagicMock, Mock, patch
from db.sql_util import SQLUtil
from pydantic import SecretStr
+from sqlalchemy import Connection, event
from db.model.chat_config import ChatConfigDB
+from db.model.chat_message import ChatMessageDB
from db.model.user import UserDB
from di.di import DI
from features.chat.attachment.chat_attachment import ChatAttachment
@@ -31,6 +34,26 @@
from util.errors import InternalError
from util.functions import generate_deterministic_short_uuid
+_ingestion_order = count(1)
+
+
+def _assign_sqlite_ingestion_order(
+ _mapper,
+ connection: Connection,
+ target: ChatMessageDB,
+) -> None:
+ if connection.dialect.name != "sqlite" or target.ingestion_order is not None:
+ return
+ target.ingestion_order = next(_ingestion_order)
+
+
+def setUpModule() -> None:
+ event.listen(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
+
+def tearDownModule() -> None:
+ event.remove(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
class TelegramChatInboundServiceTest(unittest.TestCase):
@@ -515,6 +538,7 @@ def test_store_message_new(self):
result = self.resolver.store_message(mapped_data, formatted_text, chat.chat_id, None)
saved_message = self.sql.chat_message_repo().get(chat.chat_id, mapped_data.message_id)
+ assert result is not None
self.assertEqual(result, saved_message)
self.assertEqual(result.chat_id, chat.chat_id)
self.assertEqual(result.message_id, mapped_data.message_id)
diff --git a/test/features/chat/telegram/test_telegram_update_responder.py b/test/features/chat/telegram/test_telegram_update_responder.py
index a4e0cde0..a47732fd 100644
--- a/test/features/chat/telegram/test_telegram_update_responder.py
+++ b/test/features/chat/telegram/test_telegram_update_responder.py
@@ -1,364 +1,115 @@
+import asyncio
import unittest
-from collections import namedtuple
-from datetime import date, datetime
-from unittest.mock import Mock, patch
+from datetime import datetime
+from unittest.mock import AsyncMock, MagicMock, Mock, patch
from uuid import UUID
-from db.sql_util import SQLUtil
-from langchain_core.messages import AIMessage
-
from db.model.chat_config import ChatConfigDB
-from db.model.user import UserDB
+from di.di import DI
from features.chat.config.chat_config import ChatConfig
from features.chat.ingested_chat_message import IngestedChatMessage
from features.chat.message.chat_message import ChatMessage
+from features.chat.message_burst import ScheduledChatMessageBurst
+from features.chat.message_burst_service import MessageBurstService
from features.chat.telegram.model.update import Update
-from features.chat.telegram.telegram_update_responder import respond_to_update
+from features.chat.telegram.telegram_update_responder import (
+ _ingest_update,
+ _IngressOutcome,
+ respond_to_update,
+)
+from features.integrations.integrations import resolve_agent_user, resolve_external_handle
from features.users.user import User
-from util.error_codes import PLATFORM_MAPPING_FAILED
-from util.errors import ServiceError
class TelegramUpdateResponderTest(unittest.TestCase):
- sql: SQLUtil
- update: Update
- di: Mock
- mock_sleep: Mock
-
def setUp(self):
- # create all the mocks
- self.sql = SQLUtil()
- self.update = Mock(spec = Update)
-
- patcher_sleep = patch("features.chat.telegram.telegram_update_responder.sleep")
- self.addCleanup(patcher_sleep.stop)
- self.mock_sleep = patcher_sleep.start()
-
- # mock the DI container
- patcher_di = patch("features.chat.telegram.telegram_update_responder.DI")
- self.addCleanup(patcher_di.stop)
- self.di = patcher_di.start().return_value
-
- self.di.access_token_resolver.get_access_token_for_tool.return_value = "dummy_token"
-
- # patch all dependencies in the correct namespace where they are used in telegram_update_responder
- patcher_get_detached_session = patch("features.chat.telegram.telegram_update_responder.get_detached_session")
- self.addCleanup(patcher_get_detached_session.stop)
- self.mock_get_detached_session = patcher_get_detached_session.start()
- self.mock_get_detached_session.return_value.__enter__.return_value = self.sql.start_session()
-
- # patch the DI's chat_agent and telegram_bot_sdk mocks for use in tests
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
- self.di.telegram_bot_sdk.send_text_message = Mock()
-
- def __resolved_result(
- self,
- chat_config: ChatConfig | None = None,
- author: User | None = None,
- message: ChatMessage | None = None,
- raw_message_text: str = "Test message text",
- ):
- return Mock(
- spec = IngestedChatMessage,
- chat = chat_config or ChatConfig(
- chat_id = UUID(int = 123),
- external_id = "123",
- language_name = "English",
- language_iso_code = "en",
- title = "Test Chat",
- is_private = False,
- reply_chance_percent = 100,
- release_notifications = ChatConfigDB.ReleaseNotifications.all,
- media_mode = ChatConfigDB.MediaMode.photo,
- chat_type = ChatConfigDB.ChatType.telegram,
- ),
- author = author,
- message = message or Mock(spec = ChatMessage, message_id = "test-message-id", sent_at = datetime.now()),
- raw_message_text = raw_message_text,
+ self.chat = ChatConfig(
+ chat_id = UUID(int = 10),
+ external_id = "123",
+ is_private = False,
+ reply_chance_percent = 0,
+ chat_type = ChatConfigDB.ChatType.telegram,
)
-
- def tearDown(self):
- self.sql.end_session()
-
- def test_successful_response(self):
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
-
- author = User(
- id = UUID(int = 1),
- telegram_username = "test_user",
+ self.author = User(
+ id = UUID(int = 20),
full_name = "Test User",
- telegram_user_id = 1,
- connect_key = "TG-USER-KEY1",
- group = UserDB.Group.standard,
- created_at = date.today(),
- telegram_chat_id = "123",
- )
-
- resolved = self.__resolved_result(author = author)
- self.di.telegram_chat_inbound_service.ingest_update.return_value = resolved
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.telegram_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
-
- self.di.domain_langchain_mapper.map_bot_message_to_storage.return_value = [
- Mock(chat_id = "123", text = "Test response"),
- ]
-
- result = respond_to_update(self.update)
-
- self.assertTrue(result)
- self.di.telegram_chat_inbound_service.ingest_update.assert_called_once_with(self.update)
- self.di.chat_agent.assert_called_once_with(
- trigger_message_text = "Test message text",
- trigger_message_id = "test-message-id",
- trigger_message_sent_at = resolved.message.sent_at,
- configured_tool = self.di.tool_choice_resolver.get_tool.return_value,
+ telegram_user_id = 20,
+ telegram_username = "test_user",
)
- self.di.chat_agent.return_value.execute.assert_called_once()
- self.di.telegram_bot_sdk.send_text_message.assert_called_once_with(resolved.chat, "Test response")
- self.mock_sleep.assert_called_once_with(0.1)
- self.assertEqual(events, ["rollback", "send:Test response", "rollback", "sleep"])
-
- def test_reaction_response(self):
- self.di.chat_agent.return_value.execute.return_value = AIMessage(content = [
- {"type": "thinking", "thinking": "A reaction is appropriate"},
- {"type": "text", "text": "๐"},
- ])
- self.di.telegram_chat_inbound_service.ingest_update.return_value = self.__resolved_result(
- author = Mock(spec = User, id = UUID(int = 1)),
- message = Mock(spec = ChatMessage, message_id = "test-message-id", sent_at = datetime.now()),
+ self.message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-1",
+ ingestion_order = 7,
+ author_id = self.author.id,
+ sent_at = datetime(2026, 1, 2, 12, 0, 0),
+ text = "hello",
)
-
- result = respond_to_update(self.update)
-
- self.assertTrue(result)
- self.di.platform_bot_sdk.return_value.set_reaction.assert_called_once_with("123", "test-message-id", "๐")
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
- self.mock_sleep.assert_not_called()
- saved_message = self.di.chat_message_repo.save.call_args.args[0]
- self.assertIsInstance(saved_message, ChatMessage)
- self.assertEqual(saved_message.message_id, "reaction:test-message-id")
- self.assertEqual(saved_message.text, "๐")
-
- def test_reaction_response_failure(self):
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "๐")
- self.di.platform_bot_sdk.return_value.set_reaction.side_effect = Exception("Reaction failed")
- self.di.telegram_chat_inbound_service.ingest_update.return_value = self.__resolved_result(
- author = Mock(spec = User, id = UUID(int = 1)),
- message = Mock(spec = ChatMessage, message_id = "test-message-id", sent_at = datetime.now()),
- )
-
- result = respond_to_update(self.update)
-
- self.assertTrue(result)
- self.di.platform_bot_sdk.return_value.set_reaction.assert_called_once_with("123", "test-message-id", "๐")
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
- self.mock_sleep.assert_not_called()
- saved_message = self.di.chat_message_repo.save.call_args.args[0]
- self.assertIsInstance(saved_message, ChatMessage)
- self.assertEqual(saved_message.message_id, "reaction:test-message-id")
- self.assertEqual(saved_message.text, "๐")
-
- def test_empty_response(self):
- self.di.telegram_chat_inbound_service.ingest_update.return_value = self.__resolved_result(
- author = Mock(spec = User, id = UUID(int = 1)),
+ self.ingested = IngestedChatMessage(
+ chat = self.chat,
+ author = self.author,
+ message = self.message,
+ attachments = [],
+ raw_message_text = "hello",
)
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
- self.di.chat_agent.return_value.execute.return_value = Mock(content = "")
-
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_mapping_error(self):
- raw_message = Mock(message_id = 42, chat = Mock(id = 123))
- self.update = Mock(spec = Update, edited_message = None, message = raw_message)
- self.di.telegram_chat_inbound_service.ingest_update.return_value = None
-
+ self.di = Mock(spec = DI)
+ self.burst_service = Mock(spec = MessageBurstService)
+ # noinspection PyPropertyAccess
+ self.di.message_burst_service = self.burst_service
+
+ def test_command_is_processed_without_creating_burst(self):
+ agent = resolve_agent_user(self.ingested.chat.chat_type)
+ agent_handle = resolve_external_handle(agent, self.ingested.chat.chat_type)
+ self.ingested.raw_message_text = f"/help@{agent_handle}"
+ self.di.telegram_chat_inbound_service.ingest_update.return_value = self.ingested
+ self.burst_service.process_message.return_value = False
+ session = MagicMock()
with (
- patch("features.chat.telegram.telegram_update_responder.log.d") as mock_log_debug,
- patch("features.chat.telegram.telegram_update_responder.log.e") as mock_log_error,
- patch("features.integrations.prompt_resolvers.simple_chat_error", return_value = "Mapping error"),
+ patch(
+ "features.chat.telegram.telegram_update_responder.get_detached_session",
+ return_value = session,
+ ),
+ patch(
+ "features.chat.telegram.telegram_update_responder.DI",
+ return_value = self.di,
+ ),
):
- self.di.domain_langchain_mapper.map_bot_message_to_storage.return_value = [
- Mock(chat_id = "123", text = "Mapping error"),
- ]
- result = respond_to_update(self.update)
- self.assertFalse(result)
- mock_log_debug.assert_called_once_with("No Telegram response needed (update ignored)")
- mock_log_error.assert_not_called()
-
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
- self.di.telegram_bot_sdk.set_reaction.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_no_author_returns_false(self):
- self.di.telegram_chat_inbound_service.ingest_update.return_value = self.__resolved_result(author = None)
-
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.chat_agent.assert_not_called()
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
-
- def test_mapping_failure_before_text_delivery_sends_no_error_message_or_reaction(self):
- resolved = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.telegram_chat_inbound_service.ingest_update.return_value = resolved
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
-
- error = ServiceError(
- message = "Mapping failed",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
- )
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = error
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_called_once_with(
- resolved.chat,
- self.di.chat_agent.return_value.execute.return_value,
- )
- mock_error.assert_not_called()
- self.di.telegram_bot_sdk.send_text_message.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- self.di.telegram_bot_sdk.set_reaction.assert_not_called()
- self.mock_sleep.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_first_response_send_failure_sends_one_error_text_without_reaction_or_pre_failure_sleep(self):
- resolved = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.telegram_chat_inbound_service.ingest_update.return_value = resolved
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
-
- error = ServiceError(
- message = "Test error",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
- )
- ErrorMsg = namedtuple("ErrorMsg", ["chat_id", "text"])
- response = [ErrorMsg(chat_id = "123", text = "First response")]
- error_response = [ErrorMsg(chat_id = "123", text = "Error response")]
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = [response, error_response]
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
- if text == "First response":
- raise error
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.telegram_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
+ outcome = _ingest_update(Mock(spec = Update))
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- mock_error.return_value = "Error response"
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.telegram_bot_sdk.set_reaction.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- mock_error.assert_called_once_with(str(error), emoji = "๐งจ")
- self.assertEqual(
- [call.args[1] for call in self.di.telegram_bot_sdk.send_text_message.call_args_list],
- ["First response", "Error response"],
- )
- self.assertEqual(
- events,
- ["rollback", "send:First response", "rollback", "send:Error response", "rollback", "sleep"],
+ self.assertIsNone(outcome.scheduled_burst)
+ self.burst_service.process_message.assert_called_once_with(
+ self.ingested,
+ command_only = True,
)
- self.assertEqual([call.args for call in self.mock_sleep.call_args_list], [(0.1,)])
- self.di.chat_message_repo.save.assert_not_called()
+ self.burst_service.record.assert_not_called()
- def test_general_exception_after_response_delivery_started_sends_error_without_reaction(self):
- resolved_domain_data_mock = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.telegram_chat_inbound_service.ingest_update.return_value = resolved_domain_data_mock
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
-
- error = ServiceError(
- message = "Test error",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
- )
- ErrorMsg = namedtuple("ErrorMsg", ["chat_id", "text"])
- response = [
- ErrorMsg(chat_id = "123", text = "First response"),
- ErrorMsg(chat_id = "123", text = "Second response"),
- ]
- error_response = [ErrorMsg(chat_id = "123", text = "Error response")]
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = [response, error_response]
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
- if text == "Second response":
- raise error
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.telegram_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- mock_error.return_value = "Error response"
- result = respond_to_update(self.update)
+ def test_async_responder_offloads_ingestion_and_processing(self):
+ scheduled = self._scheduled_burst()
+ self.burst_service.process_after_quiet_period.return_value = True
+ with (
+ patch(
+ "features.chat.telegram.telegram_update_responder.asyncio.to_thread",
+ new = AsyncMock(return_value = _IngressOutcome(scheduled_burst = scheduled)),
+ ) as to_thread,
+ patch(
+ "features.chat.telegram.telegram_update_responder.DI",
+ return_value = self.di,
+ ) as di_factory,
+ ):
+ result = asyncio.run(respond_to_update(Mock(spec = Update)))
- self.assertFalse(result)
- self.di.telegram_bot_sdk.set_reaction.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- mock_error.assert_called_once_with(str(error), emoji = "๐งจ")
- self.assertEqual(
- [call.args[1] for call in self.di.telegram_bot_sdk.send_text_message.call_args_list],
- ["First response", "Second response", "Error response"],
+ self.assertTrue(result)
+ to_thread.assert_awaited_once()
+ di_factory.assert_called_once_with(
+ invoker_id = scheduled.author_id.hex,
+ invoker_chat_id = scheduled.chat_id.hex,
)
- self.assertEqual([call.args for call in self.mock_sleep.call_args_list], [(0.1,), (0.1,)])
- self.assertEqual(
- events,
- [
- "rollback",
- "send:First response",
- "rollback",
- "sleep",
- "send:Second response",
- "rollback",
- "send:Error response",
- "rollback",
- "sleep",
- ],
+ self.burst_service.process_after_quiet_period.assert_awaited_once()
+
+ def _scheduled_burst(self, message_count: int = 1) -> ScheduledChatMessageBurst:
+ return ScheduledChatMessageBurst(
+ chat_id = self.chat.chat_id,
+ author_id = self.author.id,
+ message_count = message_count,
+ wait_seconds = 0.5,
)
- self.di.chat_message_repo.save.assert_not_called()
diff --git a/test/features/chat/test_chat_agent.py b/test/features/chat/test_chat_agent.py
index daf8c7f2..16239f39 100644
--- a/test/features/chat/test_chat_agent.py
+++ b/test/features/chat/test_chat_agent.py
@@ -1,5 +1,5 @@
import unittest
-from datetime import datetime, timedelta
+from datetime import datetime
from unittest.mock import MagicMock, Mock, patch
from uuid import UUID
@@ -105,11 +105,11 @@ def setUp(self):
self.configured_tool = Mock()
# Mock message/attachment fetching used in ChatAgent.__init__
- self.trigger_message_sent_at = datetime.now()
+ self.cutoff_sent_at = datetime.now()
mock_latest_message = ChatMessage(
message_id = "msg_123",
author_id = self.user.id,
- sent_at = self.trigger_message_sent_at,
+ sent_at = self.cutoff_sent_at,
text = "Test message",
chat_id = self.chat_config.chat_id,
)
@@ -118,15 +118,14 @@ def setUp(self):
self.mock_di.user_repo.get.return_value = None
self.mock_di.domain_langchain_mapper.map_to_langchain.return_value = HumanMessage("Test message")
- self.sleep_patcher = patch("features.chat.chat_agent.time.sleep")
- self.mock_sleep = self.sleep_patcher.start()
-
self.agent = ChatAgent(
trigger_message_text = "Test message",
trigger_message_id = "msg_123",
- trigger_message_sent_at = self.trigger_message_sent_at,
configured_tool = self.configured_tool,
di = self.mock_di,
+ cutoff_sent_at = self.cutoff_sent_at,
+ cutoff_ingestion_order = 7,
+ explicitly_addressed = False,
)
# reset so per-test assertions don't count the init call
self.mock_di.chat_message_repo.get_latest_by_chat.reset_mock()
@@ -145,9 +144,11 @@ def test_process_commands_no_api_key(self):
bot_no_key = ChatAgent(
trigger_message_text = "Test message",
trigger_message_id = "msg_123",
- trigger_message_sent_at = self.trigger_message_sent_at,
configured_tool = None,
di = self.mock_di,
+ cutoff_sent_at = self.cutoff_sent_at,
+ cutoff_ingestion_order = 7,
+ explicitly_addressed = False,
)
self.mock_di.command_processor.execute.return_value = CommandProcessor.Result(
@@ -189,10 +190,10 @@ def test_should_reply_private_chat(self):
self.assertTrue(self.agent.should_reply())
- def test_should_reply_bot_mentioned(self):
+ def test_should_reply_explicitly_addressed(self):
self.chat_config.is_private = False
self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = f"Hello @{self.agent_user.telegram_username}"
+ self.agent._ChatAgent__explicitly_addressed = True
self.assertTrue(self.agent.should_reply())
@@ -208,13 +209,10 @@ def test_should_not_reply_when_bot_mention_is_only_quoted(self):
self.assertFalse(self.agent.should_reply())
- def test_should_reply_when_unquoted_text_mentions_bot_after_quote(self):
+ def test_should_reply_with_aggregated_addressing_after_quote(self):
self.chat_config.is_private = False
self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = (
- f">> Hello @{self.agent_user.telegram_username}\n\n"
- f"@{self.agent_user.telegram_username} what about this?"
- )
+ self.agent._ChatAgent__explicitly_addressed = True
self.assertTrue(self.agent.should_reply())
@@ -283,46 +281,19 @@ def test_execute_no_reply_needed(self, mock_should_reply):
result = self.agent.execute()
self.assertIsNone(result)
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_command_processed(self, mock_should_reply, mock_process_commands):
- self.chat_config.is_private = True
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = True,
- reply = None,
- )
- result = self.agent.execute()
- self.assertIsNone(result)
-
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_command_failed(self, mock_should_reply, mock_process_commands):
- self.chat_config.is_private = True
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = True,
- reply = AIMessage("Failed to process command."),
- )
- result = self.agent.execute()
- self.assertIn("Failed to process command.", result.content)
-
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_no_api_key(self, mock_should_reply, mock_process_commands):
+ def test_execute_no_api_key(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
# Create a new bot instance without configured_tool (simulating no API key)
bot_no_key = ChatAgent(
trigger_message_text = "Test message",
trigger_message_id = "msg_123",
- trigger_message_sent_at = self.trigger_message_sent_at,
configured_tool = None,
di = self.mock_di,
+ cutoff_sent_at = self.cutoff_sent_at,
+ cutoff_ingestion_order = 7,
+ explicitly_addressed = False,
)
result = bot_no_key.execute()
@@ -330,14 +301,9 @@ def test_execute_no_api_key(self, mock_should_reply, mock_process_commands):
self.mock_platform_sdk.send_text_message.assert_called_once()
self.assertIn("Not configured", self.mock_platform_sdk.send_text_message.call_args[0][1])
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_llm_response(self, mock_should_reply, mock_process_commands):
+ def test_execute_llm_response(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
# Mock the tools_model invoke to return the final response
mock_tools_model = Mock()
@@ -347,14 +313,9 @@ def test_execute_llm_response(self, mock_should_reply, mock_process_commands):
result = self.agent.execute()
self.assertEqual(result.content, "LLM response")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_removes_attachment_placeholder_from_llm_response(self, mock_should_reply, mock_process_commands):
+ def test_execute_removes_attachment_placeholder_from_llm_response(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
mock_tools_model = Mock()
mock_tools_model.invoke.return_value = AIMessage("Here you go\n\n๐ [ a1 (image/png) ]\n\nDone")
self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
@@ -363,14 +324,9 @@ def test_execute_removes_attachment_placeholder_from_llm_response(self, mock_sho
self.assertEqual(result.content, "Here you go\n\nDone")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_removes_attachment_placeholder_only_llm_response(self, mock_should_reply, mock_process_commands):
+ def test_execute_removes_attachment_placeholder_only_llm_response(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
mock_tools_model = Mock()
mock_tools_model.invoke.return_value = AIMessage("๐ [ a1 (image/png) ]")
self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
@@ -379,18 +335,12 @@ def test_execute_removes_attachment_placeholder_only_llm_response(self, mock_sho
self.assertEqual(result.content, "")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
def test_execute_removes_attachment_placeholder_from_llm_content_blocks(
self,
mock_should_reply,
- mock_process_commands,
):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
mock_tools_model = Mock()
mock_tools_model.invoke.return_value = AIMessage(content = [
{"type": "text", "text": "Here\n๐ [ a1 (image/png) ]"},
@@ -406,14 +356,9 @@ def test_execute_removes_attachment_placeholder_from_llm_content_blocks(
{"type": "thinking", "thinking": "internal"},
])
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_tool_call(self, mock_should_reply, mock_process_commands):
+ def test_execute_tool_call(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
tool_call = {"id": "1", "name": "test_tool", "args": {}}
# Create AI messages with tool_calls attribute
@@ -429,14 +374,9 @@ def test_execute_tool_call(self, mock_should_reply, mock_process_commands):
result = self.agent.execute()
self.assertEqual(result.content, "Final response")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_exception(self, mock_should_reply, mock_process_commands):
+ def test_execute_exception(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
# Mock the tools_model to raise an exception
mock_tools_model = Mock()
@@ -449,16 +389,9 @@ def test_execute_exception(self, mock_should_reply, mock_process_commands):
self.assertIn("Test error", self.mock_platform_sdk.send_text_message.call_args[0][1])
self.mock_platform_sdk.send_button_link.assert_called_once()
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_max_iterations_exceeded(self, mock_should_reply, mock_process_commands, mock_config):
+ def test_execute_max_iterations_exceeded(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
- mock_config.chat_debounce_delay_s = 0.0
self.agent._ChatAgent__max_iterations = 2
# Create AI messages with tool_calls to simulate continued iterations
@@ -481,14 +414,9 @@ def test_execute_max_iterations_exceeded(self, mock_should_reply, mock_process_c
self.assertIn("Reached max iterations", sent_text)
self.assertIn("2", sent_text)
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_waitlist_guard_blocks_unknown_commands(self, mock_should_reply, mock_process_commands):
+ def test_execute_waitlist_guard_blocks_unknown_commands(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
"Waitlisted account is not active yet",
WAITLIST_ACCOUNT_NOT_ACTIVE,
@@ -499,32 +427,9 @@ def test_execute_waitlist_guard_blocks_unknown_commands(self, mock_should_reply,
self.assertEqual(result.content, "๐")
self.assertIn("waitlist", self.mock_platform_sdk.send_text_message.call_args[0][1].lower())
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_waitlist_guard_does_not_override_command_failure(self, mock_should_reply, mock_process_commands):
- self.chat_config.is_private = True
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = True,
- reply = AIMessage("Failed to process command."),
- )
- self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
- "Waitlisted account is not active yet",
- WAITLIST_ACCOUNT_NOT_ACTIVE,
- )
-
- result = self.agent.execute()
- self.assertIsNotNone(result)
- self.assertIn("Failed to process command.", result.content)
-
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_policy_guard_blocks_active_user_without_policy(self, mock_should_reply, mock_process_commands):
+ def test_execute_policy_guard_blocks_active_user_without_policy(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
"Accept policies in /settings first.",
WAITLIST_INVITED_POLICIES_REQUIRED,
@@ -535,797 +440,50 @@ def test_execute_policy_guard_blocks_active_user_without_policy(self, mock_shoul
self.assertEqual(result.content, "๐")
self.assertIn("policies", self.mock_platform_sdk.send_text_message.call_args[0][1].lower())
- @patch("features.chat.chat_agent.config")
- def test_execute_rolls_back_before_debounce_sleep_and_after_query(self, mock_config):
- events = []
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- def get_latest_by_chat(chat_id, limit):
- events.append("query")
- return [newer_message]
-
- self.mock_di.rollback_db_session.reset_mock()
- self.mock_di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
- mock_config.chat_debounce_delay_s = 1.0
- newer_message = Mock()
- newer_message.message_id = "msg_999"
- newer_message.author_id = self.user.id
- newer_message.sent_at = self.trigger_message_sent_at + timedelta(seconds = 1)
- self.mock_di.chat_message_repo.get_latest_by_chat.side_effect = get_latest_by_chat
-
- result = self.agent.execute()
-
- self.assertIsNone(result)
- self.assertEqual(events, ["rollback", "sleep", "query", "rollback"])
-
- def tearDown(self):
- self.sleep_patcher.stop()
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_skips_debounce_when_delay_is_zero(self, mock_should_reply, mock_process_commands, mock_config):
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 0.0
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("LLM response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.assertEqual(result.content, "LLM response")
- self.mock_sleep.assert_not_called()
- self.mock_di.chat_message_repo.get_latest_by_chat.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_is_superseded_by_newer_invoker_message_proceeds_when_message_is_latest(
- self, mock_should_reply, mock_process_commands, mock_config,
- ):
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("LLM response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.mock_sleep.assert_called_once_with(1.0)
- self.assertEqual(result.content, "LLM response")
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_is_superseded_by_newer_invoker_message_skips_llm_when_newer_message_exists(
- self, mock_should_reply, mock_process_commands, mock_config,
- ):
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- newer_message = Mock()
- newer_message.message_id = "msg_999"
- newer_message.author_id = self.user.id
- newer_message.sent_at = self.trigger_message_sent_at + timedelta(seconds = 1)
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [newer_message]
-
- result = self.agent.execute()
-
- self.mock_sleep.assert_called_once_with(1.0)
- self.assertIsNone(result)
- self.mock_di.llm_tool_library.bind_tools.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_is_superseded_by_newer_invoker_message_keeps_edited_message_newer_than_higher_numeric_id(
- self, mock_should_reply, mock_config,
- ):
- mock_should_reply.return_value = True
- mock_config.chat_debounce_delay_s = 1.0
- edited_at = datetime(2026, 1, 1, 12, 0)
- older_sent_at = edited_at - timedelta(seconds = 1)
- current_edited_message = ChatMessage(
- message_id = "100",
- author_id = self.user.id,
- sent_at = edited_at,
- text = "edited older Telegram message",
- chat_id = self.chat_config.chat_id,
- )
- higher_numeric_prior_message = ChatMessage(
- message_id = "101",
- author_id = self.user.id,
- sent_at = older_sent_at,
- text = "prior Telegram message",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.side_effect = [
- [current_edited_message, higher_numeric_prior_message],
- [higher_numeric_prior_message],
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("LLM response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
- agent = ChatAgent(
- trigger_message_text = "edited older Telegram message",
- trigger_message_id = "100",
- trigger_message_sent_at = edited_at,
- configured_tool = self.configured_tool,
- di = self.mock_di,
- )
-
- result = agent.execute()
-
- self.mock_sleep.assert_called_once_with(1.0)
- self.assertEqual(result.content, "LLM response")
+ def test_init_bounds_history_to_claimed_cutoff(self):
+ self.mock_di.chat_message_repo.get_latest_by_chat.reset_mock()
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_is_superseded_by_newer_invoker_message_skips_older_edit_with_same_message_id(
- self, mock_should_reply, mock_config,
- ):
- mock_should_reply.return_value = True
- mock_config.chat_debounce_delay_s = 1.0
- older_edit_at = datetime(2026, 1, 1, 12, 0)
- newer_edit_at = older_edit_at + timedelta(seconds = 1)
- newer_same_message = ChatMessage(
- message_id = "100",
- author_id = self.user.id,
- sent_at = newer_edit_at,
- text = "newer Telegram edit",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.side_effect = [
- [newer_same_message],
- [newer_same_message],
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("LLM response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
- agent = ChatAgent(
- trigger_message_text = "older Telegram edit",
- trigger_message_id = "100",
- trigger_message_sent_at = older_edit_at,
+ ChatAgent(
+ trigger_message_text = "Test message",
+ trigger_message_id = "msg_123",
configured_tool = self.configured_tool,
di = self.mock_di,
+ cutoff_sent_at = self.cutoff_sent_at,
+ cutoff_ingestion_order = 7,
+ explicitly_addressed = True,
)
- result = agent.execute()
-
- self.mock_sleep.assert_called_once_with(1.0)
- self.assertIsNone(result)
- self.mock_di.llm_tool_library.bind_tools.assert_not_called()
-
- def test_is_addressable_private_chat(self):
- self.chat_config.is_private = True
- self.agent._ChatAgent__trigger_message_text = "anything"
-
- self.assertTrue(self.agent._ChatAgent__is_addressable())
-
- def test_is_addressable_group_chat_with_mention(self):
- self.chat_config.is_private = False
- self.agent._ChatAgent__trigger_message_text = f"hello @{self.agent_user.telegram_username}"
-
- self.assertTrue(self.agent._ChatAgent__is_addressable())
-
- def test_is_addressable_group_chat_without_mention(self):
- self.chat_config.is_private = False
- self.agent._ChatAgent__trigger_message_text = "hello"
-
- self.assertFalse(self.agent._ChatAgent__is_addressable())
-
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_execute_skips_commands_when_not_addressable(self, mock_should_reply, mock_process_commands):
- self.chat_config.is_private = False
- self.agent._ChatAgent__trigger_message_text = "hello"
- mock_should_reply.return_value = False
-
- result = self.agent.execute()
-
- self.assertIsNone(result)
- mock_process_commands.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_carries_mention_from_recent_burst_message(self, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- recent_tagged = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = f"Hello @{self.agent_user.telegram_username}",
+ self.mock_di.chat_message_repo.get_latest_by_chat.assert_called_once_with(
chat_id = self.chat_config.chat_id,
+ limit = 30,
+ cutoff_sent_at = self.cutoff_sent_at,
+ cutoff_ingestion_order = 7,
)
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, recent_tagged]
-
- self.assertTrue(self.agent.should_reply())
- @patch("features.chat.chat_agent.config")
- def test_should_not_carry_quoted_mention_from_recent_burst_message(self, mock_config):
+ def test_should_reply_to_explicitly_addressed_group_burst(self):
self.chat_config.is_private = False
self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- quoted_tag = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = f">>>> Hello @{self.agent_user.telegram_username}\n\nI agree",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, quoted_tag]
-
- self.assertFalse(self.agent.should_reply())
-
- @patch("features.chat.chat_agent.config")
- def test_should_carry_unquoted_mention_after_quote_from_recent_burst_message(self, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- directly_tagged = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = (
- ">> earlier context\n\n"
- f"@{self.agent_user.telegram_username} what about this?"
- ),
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, directly_tagged]
+ self.agent._ChatAgent__explicitly_addressed = True
self.assertTrue(self.agent.should_reply())
- @patch("features.chat.chat_agent.config")
- def test_should_reply_ignores_mention_from_different_invoker(self, mock_config):
+ def test_should_not_reply_to_unaddressed_zero_chance_group_burst(self):
self.chat_config.is_private = False
self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- other_tagged = ChatMessage(
- message_id = "msg_001",
- author_id = UUID(int = 999),
- sent_at = datetime.now(),
- text = f"Hello @{self.agent_user.telegram_username}",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, other_tagged]
-
- self.assertFalse(self.agent.should_reply())
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_ignores_mention_after_bot_response(self, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- bot_reply = ChatMessage(
- message_id = "msg_002",
- author_id = self.agent_user.id,
- sent_at = datetime.now(),
- text = "you're welcome",
- chat_id = self.chat_config.chat_id,
- )
- old_tagged = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = f"Hello @{self.agent_user.telegram_username}",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, bot_reply, old_tagged]
+ self.agent._ChatAgent__explicitly_addressed = False
self.assertFalse(self.agent.should_reply())
- @patch("features.chat.chat_agent.config")
- def test_should_reply_skips_chain_walk_when_debounce_disabled(self, mock_config):
- # debounce=0 turns off burst coordination, so carry-over must not run โ otherwise
- # an untagged follow-up could double-respond alongside the still-running tagged
- # message's instance (no chain-break in DB yet).
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up with no tag"
- mock_config.chat_debounce_delay_s = 0.0
- mock_config.chat_history_depth = 30
- recent_tagged = Mock()
- recent_tagged.message_id = "msg_001"
- recent_tagged.author_id = self.user.id
- recent_tagged.sent_at = datetime.now()
- recent_tagged.text = f"Hello @{self.agent_user.telegram_username}"
- current = Mock()
- current.message_id = "msg_123"
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, recent_tagged]
-
- self.assertFalse(self.agent.should_reply())
- # the chain walk must not even hit the DB when debounce is disabled
- self.mock_di.chat_message_repo.get_latest_by_chat.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_direct_mention_works_when_debounce_disabled(self, mock_config):
- # direct mention in the current message must always trigger a reply, even with
- # the chain walk disabled by debounce=0
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = f"hey @{self.agent_user.telegram_username}"
- mock_config.chat_debounce_delay_s = 0.0
- mock_config.chat_history_depth = 30
-
- self.assertTrue(self.agent.should_reply())
- self.mock_di.chat_message_repo.get_latest_by_chat.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_skips_command_message_in_burst(self, mock_config):
- # Command messages tag the bot as part of syntax, not as a conversational mention.
- # The chain walk must skip them so a follow-up does not inherit the command's tag,
- # even when the command's bot reply is racing to land in the DB.
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "hey guys what's up"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- command_message = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = f"/help@{self.agent_user.telegram_username}",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "hey guys what's up",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [current, command_message]
-
- self.assertFalse(self.agent.should_reply())
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_carries_mention_from_seconds_old_burst_message(self, mock_config):
- # Regression for prod bug: bot did not reply to msg3 (no tag) when msg2 (TAG)
- # arrived within seconds. The should_reply call always happens after a debounce
- # sleep, so any prior burst message is older than debounce_delay_s by definition
- # โ a cutoff of now - debounce_delay_s excludes exactly the messages we want to
- # carry the mention from.
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up with no tag"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- tagged_older = ChatMessage(
- message_id = "msg_002",
- author_id = self.user.id,
- sent_at = datetime.now() - timedelta(seconds = 5),
- text = f"@{self.agent_user.telegram_username} this message should trigger a response",
- chat_id = self.chat_config.chat_id,
- )
- earlier_untagged = ChatMessage(
- message_id = "msg_001",
- author_id = self.user.id,
- sent_at = datetime.now() - timedelta(seconds = 60),
- text = "I think I fixed it",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up with no tag",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- current, tagged_older, earlier_untagged,
- ]
-
- self.assertTrue(self.agent.should_reply())
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- @patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_is_superseded_by_newer_invoker_message_ignores_newer_message_from_different_author(
- self, mock_should_reply, mock_process_commands, mock_config,
- ):
- mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
-
- newer_message_other_user = Mock()
- newer_message_other_user.message_id = "msg_999"
- newer_message_other_user.author_id = UUID(int = 999)
- our_message = Mock()
- our_message.message_id = "msg_123"
- our_message.author_id = self.user.id
- our_message.sent_at = self.trigger_message_sent_at
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- newer_message_other_user, our_message,
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("LLM response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.assertEqual(result.content, "LLM response")
-
- @patch("features.chat.chat_agent.config")
- def test_should_reply_carries_mention_past_other_user_messages(self, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = "follow up with no tag"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- other_user_msg = ChatMessage(
- message_id = "msg_003",
- author_id = UUID(int = 888),
- sent_at = datetime.now(),
- text = "some unrelated message",
- chat_id = self.chat_config.chat_id,
- )
- tagged_by_invoker = ChatMessage(
- message_id = "msg_002",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = f"@{self.agent_user.telegram_username} help me",
- chat_id = self.chat_config.chat_id,
- )
- current = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "follow up with no tag",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- current, other_user_msg, tagged_by_invoker,
- ]
-
- self.assertTrue(self.agent.should_reply())
-
- # --- GROUP 1a: burst from same user, all tagged ---
-
- @patch("features.chat.chat_agent.config")
- def test_group_burst_same_user_all_tagged_carries_mention(self, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- self.agent._ChatAgent__trigger_message_text = f"@{self.agent_user.telegram_username} second thought"
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
-
- self.assertTrue(self.agent.should_reply())
-
- # --- GROUP 1c: burst from same user, first untagged then tagged ---
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_group_burst_same_user_tagged_winner_replies(self, mock_process_commands, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
-
- mock_config.chat_history_depth = 30
- self.agent._ChatAgent__trigger_message_text = f"@{self.agent_user.telegram_username} actually this"
- our_message = Mock()
- our_message.message_id = "msg_123"
- our_message.author_id = self.user.id
- our_message.sent_at = self.trigger_message_sent_at
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [our_message]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("response")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.assertEqual(result.content, "response")
-
- # --- GROUP 2c: burst from different users, first untagged then tagged ---
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_group_burst_different_users_tagged_user_not_suppressed(self, mock_process_commands, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
-
- mock_config.chat_history_depth = 30
- self.agent._ChatAgent__trigger_message_text = f"@{self.agent_user.telegram_username} hey bot"
- newer_from_other = Mock()
- newer_from_other.message_id = "msg_200"
- newer_from_other.author_id = UUID(int = 888)
- our_tagged = Mock()
- our_tagged.message_id = "msg_123"
- our_tagged.author_id = self.user.id
- our_tagged.sent_at = self.trigger_message_sent_at
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- newer_from_other, our_tagged,
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("bot reply")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.assertEqual(result.content, "bot reply")
-
- # --- GROUP 2d: burst from different users, no tags ---
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_group_burst_different_users_no_tags_no_reply(self, mock_process_commands, mock_config):
- self.chat_config.is_private = False
- self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- self.agent._ChatAgent__trigger_message_text = "just chatting"
- newer_from_other = ChatMessage(
- message_id = "msg_200",
- author_id = UUID(int = 888),
- sent_at = datetime.now(),
- text = "hey everyone",
- chat_id = self.chat_config.chat_id,
- )
- our_message = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = datetime.now(),
- text = "just chatting",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- newer_from_other, our_message,
- ]
-
- result = self.agent.execute()
-
- self.assertIsNone(result)
-
- # --- SINGLE 1: burst in private chat ---
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_private_burst_winner_always_replies(self, mock_process_commands, mock_config):
- self.chat_config.is_private = True
- self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
-
- mock_config.chat_history_depth = 30
- self.agent._ChatAgent__trigger_message_text = "last message in burst"
- our_message = ChatMessage(
- message_id = "msg_123",
- author_id = self.user.id,
- sent_at = self.trigger_message_sent_at,
- text = "last message in burst",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [our_message]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("private reply")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = self.agent.execute()
-
- self.assertEqual(result.content, "private reply")
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_private_burst_same_second_older_numeric_message_does_not_suppress_current(
- self, mock_process_commands, mock_config,
- ):
- self.chat_config.is_private = True
- self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- sent_at = datetime(2026, 1, 1, 12, 0, 0)
- older_message = ChatMessage(
- message_id = "6738",
- author_id = self.user.id,
- sent_at = sent_at,
- text = "older private message",
- chat_id = self.chat_config.chat_id,
- )
- current_message = ChatMessage(
- message_id = "6739",
- author_id = self.user.id,
- sent_at = sent_at,
- text = "current private message",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- older_message, current_message,
- ]
- agent = ChatAgent(
- trigger_message_text = "current private message",
- trigger_message_id = "6739",
- trigger_message_sent_at = current_message.sent_at,
- configured_tool = self.configured_tool,
- di = self.mock_di,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.reset_mock()
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- older_message, current_message,
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("private reply")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = agent.execute()
-
- self.assertEqual(result.content, "private reply")
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_private_burst_current_numeric_message_replies_when_missing_from_recent_query(
- self, mock_process_commands, mock_config,
- ):
+ def test_should_reply_to_private_burst_without_explicit_address(self):
self.chat_config.is_private = True
self.chat_config.reply_chance_percent = 0
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- older_sent_at = datetime(2026, 1, 1, 12, 0, 0)
- current_message = ChatMessage(
- message_id = "6739",
- author_id = self.user.id,
- sent_at = older_sent_at + timedelta(seconds = 1),
- text = "current private message",
- chat_id = self.chat_config.chat_id,
- )
- older_same_author = ChatMessage(
- message_id = "6738",
- author_id = self.user.id,
- sent_at = older_sent_at,
- text = "older private message",
- chat_id = self.chat_config.chat_id,
- )
- bot_context = ChatMessage(
- message_id = "6737",
- author_id = self.agent_user.id,
- sent_at = older_sent_at - timedelta(seconds = 1),
- text = "previous bot response",
- chat_id = self.chat_config.chat_id,
- )
- other_author_context = ChatMessage(
- message_id = "6736",
- author_id = UUID(int = 2),
- sent_at = older_sent_at - timedelta(seconds = 2),
- text = "other author context",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- older_same_author, bot_context, other_author_context,
- ]
- agent = ChatAgent(
- trigger_message_text = current_message.text,
- trigger_message_id = current_message.message_id,
- trigger_message_sent_at = current_message.sent_at,
- configured_tool = self.configured_tool,
- di = self.mock_di,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.reset_mock()
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [
- older_same_author, bot_context, other_author_context,
- ]
- mock_tools_model = Mock()
- mock_tools_model.invoke.return_value = AIMessage("private reply")
- self.mock_di.llm_tool_library.bind_tools.return_value = mock_tools_model
-
- result = agent.execute()
+ self.agent._ChatAgent__explicitly_addressed = False
- self.assertEqual(result.content, "private reply")
- mock_tools_model.invoke.assert_called_once()
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_private_burst_older_numeric_message_suppressed_when_current_missing_from_history(
- self, mock_process_commands, mock_config,
- ):
- self.chat_config.is_private = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- mock_config.chat_history_depth = 30
- newer_message = ChatMessage(
- message_id = "6740",
- author_id = self.user.id,
- sent_at = datetime(2026, 1, 1, 12, 0, 1),
- text = "newer private message",
- chat_id = self.chat_config.chat_id,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [newer_message]
- agent = ChatAgent(
- trigger_message_text = "older private message",
- trigger_message_id = "6739",
- trigger_message_sent_at = datetime(2026, 1, 1, 12, 0, 0),
- configured_tool = self.configured_tool,
- di = self.mock_di,
- )
- self.mock_di.chat_message_repo.get_latest_by_chat.reset_mock()
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [newer_message]
-
- result = agent.execute()
-
- self.assertIsNone(result)
- self.mock_di.llm_tool_library.bind_tools.assert_not_called()
-
- @patch("features.chat.chat_agent.config")
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
- def test_private_burst_older_message_suppressed(self, mock_process_commands, mock_config):
- self.chat_config.is_private = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(is_handled = False, reply = None)
- mock_config.chat_debounce_delay_s = 1.0
- self.agent._ChatAgent__trigger_message_text = "first message"
- newer_message = Mock()
- newer_message.message_id = "msg_999"
- newer_message.author_id = self.user.id
- newer_message.sent_at = self.trigger_message_sent_at + timedelta(seconds = 1)
- self.mock_di.chat_message_repo.get_latest_by_chat.return_value = [newer_message]
-
- result = self.agent.execute()
-
- self.assertIsNone(result)
+ self.assertTrue(self.agent.should_reply())
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_error_routes_to_private_chat_with_settings_link(self, mock_should_reply, mock_process_commands):
+ def test_error_routes_to_private_chat_with_settings_link(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
"Some auth error",
WAITLIST_ACCOUNT_NOT_ACTIVE,
@@ -1343,14 +501,9 @@ def test_error_routes_to_private_chat_with_settings_link(self, mock_should_reply
"https://example.com/settings",
)
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_error_falls_back_to_inline_when_no_private_chat(self, mock_should_reply, mock_process_commands):
+ def test_error_falls_back_to_inline_when_no_private_chat(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
self.user.telegram_chat_id = None
self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
"Some auth error",
@@ -1363,14 +516,9 @@ def test_error_falls_back_to_inline_when_no_private_chat(self, mock_should_reply
self.mock_platform_sdk.send_text_message.assert_not_called()
self.mock_platform_sdk.send_button_link.assert_not_called()
- @patch("features.chat.chat_agent.ChatAgent.process_commands")
@patch("features.chat.chat_agent.ChatAgent.should_reply")
- def test_error_routing_swallows_private_chat_delivery_failure(self, mock_should_reply, mock_process_commands):
+ def test_error_routing_swallows_private_chat_delivery_failure(self, mock_should_reply):
mock_should_reply.return_value = True
- mock_process_commands.return_value = ChatAgent.CommandHandlingResult(
- is_handled = False,
- reply = None,
- )
self.mock_platform_sdk.send_text_message.side_effect = Exception("Network error")
self.mock_di.authorization_service.require_user_is_chat_ready.side_effect = AuthorizationError(
"Some auth error",
diff --git a/test/features/chat/test_message_burst_repo.py b/test/features/chat/test_message_burst_repo.py
new file mode 100644
index 00000000..6a61a853
--- /dev/null
+++ b/test/features/chat/test_message_burst_repo.py
@@ -0,0 +1,183 @@
+import unittest
+from datetime import datetime, timedelta, timezone
+from types import SimpleNamespace
+from unittest.mock import Mock
+from uuid import UUID
+
+from db.sql_util import SQLUtil
+
+from db.model.chat_config import ChatConfigDB
+from db.model.chat_message_burst import ChatMessageBurstDB
+from features.chat.config.chat_config import ChatConfig
+from features.chat.message.chat_message import ChatMessage
+from features.chat.message.chat_message_repo import ChatMessageRepository
+from features.chat.message_burst import ClaimedChatMessageBurst, ScheduledChatMessageBurst
+from features.chat.message_burst_repo import ChatMessageBurstRepository
+from features.users.user import User
+
+
+class ChatMessageBurstRepositoryTest(unittest.TestCase):
+
+ sql: SQLUtil
+ message_repo: ChatMessageRepository
+ repo: ChatMessageBurstRepository
+
+ def setUp(self):
+ self.sql = SQLUtil()
+ self.message_repo = self.sql.chat_message_repo()
+ self.repo = self.sql.chat_message_burst_repo()
+
+ def tearDown(self):
+ self.sql.end_session()
+
+ def _create_chat(self, external_id: str) -> ChatConfig:
+ return self.sql.chat_config_repo().save(ChatConfig(
+ external_id = external_id,
+ chat_type = ChatConfigDB.ChatType.telegram,
+ ))
+
+ def _create_user(self, external_id: int):
+ return self.sql.user_repo().save(User(
+ full_name = f"User {external_id}",
+ telegram_user_id = external_id,
+ ))
+
+ def test_claim_and_finalization_preserve_newer_messages(self):
+ chat = self._create_chat("chat1")
+ author = self._create_user(1)
+ first_message = self.message_repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "message1",
+ ingestion_order = 1,
+ author_id = author.id,
+ sent_at = datetime(2026, 1, 2, 12, 0, 0),
+ text = "First",
+ ))
+ first_scheduled = self.repo.record_message(
+ first_message,
+ is_addressed = False,
+ quiet_period_s = 0,
+ )
+
+ first_claim = self.repo.claim(first_scheduled)
+
+ self.assertIsNotNone(first_claim)
+ self.assertIsNone(self.repo.claim(first_scheduled))
+
+ second_message = self.message_repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "message2",
+ ingestion_order = 2,
+ author_id = author.id,
+ sent_at = first_message.sent_at,
+ text = "Second",
+ ))
+ second_scheduled = self.repo.record_message(
+ second_message,
+ is_addressed = True,
+ quiet_period_s = 0,
+ )
+
+ next_scheduled = self.repo.finalize(first_claim)
+
+ self.assertEqual(next_scheduled, second_scheduled)
+ second_claim = self.repo.claim(second_scheduled)
+ self.assertIsNotNone(second_claim)
+ self.assertTrue(second_claim.is_addressed)
+ self.assertEqual(second_claim.last_message_ingestion_order, second_message.ingestion_order)
+
+ completed = self.repo.finalize(second_claim)
+
+ self.assertIsNone(completed)
+ self.assertEqual(self.sql.get_session().query(ChatMessageBurstDB).count(), 0)
+
+ def test_delete_older_than_uses_strict_cutoff(self):
+ chat = self._create_chat("chat1")
+ author = self._create_user(1)
+ message = self.message_repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "message1",
+ ingestion_order = 1,
+ author_id = author.id,
+ text = "Message",
+ ))
+ self.repo.record_message(
+ message,
+ is_addressed = False,
+ quiet_period_s = 0,
+ )
+ process_after = self.sql.get_session().query(ChatMessageBurstDB.process_after).scalar()
+
+ boundary_count = self.repo.delete_older_than(process_after)
+ deleted_count = self.repo.delete_older_than(process_after + timedelta(seconds = 1))
+
+ self.assertEqual(boundary_count, 0)
+ self.assertEqual(deleted_count, 1)
+ self.assertEqual(self.sql.get_session().query(ChatMessageBurstDB).count(), 0)
+
+ def test_finalize_normalizes_timezone_aware_database_time(self):
+ database_now = datetime(2026, 1, 2, 12, 0, 0, tzinfo = timezone.utc)
+ queued = SimpleNamespace(
+ message_count = 2,
+ process_after = datetime(2026, 1, 2, 12, 0, 0, 250000),
+ )
+ db = Mock()
+ db.execute.side_effect = [
+ Mock(scalar_one = Mock(return_value = database_now)),
+ Mock(one_or_none = Mock(return_value = None)),
+ Mock(one_or_none = Mock(return_value = queued)),
+ ]
+ repository = ChatMessageBurstRepository(db)
+ claim = ClaimedChatMessageBurst(
+ chat_id = UUID(int = 1),
+ author_id = UUID(int = 2),
+ message_count = 1,
+ last_message_sent_at = datetime(2026, 1, 2, 12, 0, 0),
+ last_message_ingestion_order = 1,
+ is_addressed = False,
+ )
+
+ result = repository.finalize(claim)
+
+ self.assertEqual(
+ result,
+ ScheduledChatMessageBurst(
+ chat_id = claim.chat_id,
+ author_id = claim.author_id,
+ message_count = 2,
+ wait_seconds = 0.25,
+ ),
+ )
+
+ def test_new_message_count_supersedes_old_schedule_before_claim(self):
+ chat = self._create_chat("chat1")
+ author = self._create_user(1)
+ first = self.message_repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "first",
+ ingestion_order = 1,
+ author_id = author.id,
+ text = "First",
+ ))
+ second = self.message_repo.save(ChatMessage(
+ chat_id = chat.chat_id,
+ message_id = "second",
+ ingestion_order = 2,
+ author_id = author.id,
+ text = "Second",
+ ))
+ first_scheduled = self.repo.record_message(
+ first,
+ is_addressed = True,
+ quiet_period_s = 0,
+ )
+ second_scheduled = self.repo.record_message(
+ second,
+ is_addressed = False,
+ quiet_period_s = 0,
+ )
+
+ self.assertIsNone(self.repo.claim(first_scheduled))
+ claim = self.repo.claim(second_scheduled)
+ self.assertIsNotNone(claim)
+ self.assertTrue(claim.is_addressed)
diff --git a/test/features/chat/test_message_burst_service.py b/test/features/chat/test_message_burst_service.py
new file mode 100644
index 00000000..6158b64c
--- /dev/null
+++ b/test/features/chat/test_message_burst_service.py
@@ -0,0 +1,217 @@
+import asyncio
+import unittest
+from datetime import datetime
+from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
+from uuid import UUID
+
+from langchain_core.messages import AIMessage
+
+from db.model.chat_config import ChatConfigDB
+from di.di import DI
+from features.chat.config.chat_config import ChatConfig
+from features.chat.ingested_chat_message import IngestedChatMessage
+from features.chat.message.chat_message import ChatMessage
+from features.chat.message_burst import ClaimedChatMessageBurst, ScheduledChatMessageBurst
+from features.chat.message_burst_service import MessageBurstService
+from features.integrations.integrations import resolve_agent_user, resolve_external_handle
+from features.users.user import User
+
+
+class MessageBurstServiceTest(unittest.TestCase):
+
+ def setUp(self):
+ self.di = Mock(spec = DI)
+ self.service = MessageBurstService(self.di)
+ self.chat = ChatConfig(
+ chat_id = UUID(int = 10),
+ external_id = "123",
+ is_private = False,
+ reply_chance_percent = 0,
+ chat_type = ChatConfigDB.ChatType.telegram,
+ )
+ self.author = User(
+ id = UUID(int = 20),
+ full_name = "Test User",
+ telegram_user_id = 20,
+ telegram_username = "test_user",
+ whatsapp_user_id = "20",
+ )
+ self.message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-1",
+ ingestion_order = 7,
+ author_id = self.author.id,
+ sent_at = datetime(2026, 1, 2, 12, 0, 0),
+ text = "hello",
+ )
+ self.ingested = IngestedChatMessage(
+ chat = self.chat,
+ author = self.author,
+ message = self.message,
+ attachments = [],
+ raw_message_text = "hello",
+ )
+ self.di.tool_choice_resolver.get_tool.return_value = Mock()
+ self.di.chat_agent.return_value.execute.return_value = AIMessage("response")
+ self.di.domain_langchain_mapper.map_bot_message_to_storage.return_value = [
+ Mock(text = "response"),
+ ]
+
+ def test_explicit_address_ignores_quoted_mentions(self):
+ chat_type = ChatConfigDB.ChatType.telegram
+ handle = resolve_external_handle(resolve_agent_user(chat_type), chat_type)
+
+ self.assertTrue(self.service.is_explicitly_addressed(f"hello @{handle}", chat_type))
+ self.assertFalse(self.service.is_explicitly_addressed(f">> hello @{handle}\nnot addressed", chat_type))
+
+ def test_claimed_message_uses_cutoff_and_aggregate_addressing(self):
+ claim = self._claim(message_count = 1, is_addressed = True)
+
+ result = self.service.process_message(self.ingested, claim = claim)
+
+ self.assertTrue(result)
+ self.di.chat_agent.assert_called_once_with(
+ trigger_message_text = "hello",
+ trigger_message_id = "message-1",
+ configured_tool = self.di.tool_choice_resolver.get_tool.return_value,
+ cutoff_sent_at = claim.last_message_sent_at,
+ cutoff_ingestion_order = claim.last_message_ingestion_order,
+ explicitly_addressed = True,
+ )
+ self.di.chat_agent.return_value.execute.assert_called_once()
+ self.di.telegram_bot_sdk.send_text_message.assert_called_once_with(
+ self.chat,
+ "response",
+ )
+
+ def test_reaction_response_is_stored_and_sent(self):
+ self.di.chat_agent.return_value.execute.return_value = AIMessage("๐")
+
+ result = self.service.process_message(
+ self.ingested,
+ claim = self._claim(message_count = 1, is_addressed = True),
+ )
+
+ self.assertTrue(result)
+ saved = self.di.chat_message_repo.save.call_args.args[0]
+ self.assertEqual(saved.message_id, "reaction:message-1")
+ self.assertEqual(saved.text, "๐")
+ self.di.platform_bot_sdk.return_value.set_reaction.assert_called_once_with(
+ "123",
+ "message-1",
+ "๐",
+ )
+
+ def test_whatsapp_message_marks_final_message_read(self):
+ self.chat.chat_type = ChatConfigDB.ChatType.whatsapp
+
+ result = self.service.process_message(
+ self.ingested,
+ claim = self._claim(message_count = 1, is_addressed = False),
+ )
+
+ self.assertTrue(result)
+ self.di.whatsapp_bot_sdk.send_text_message.assert_called_once_with(
+ self.chat,
+ "response",
+ )
+ self.di.whatsapp_bot_sdk.mark_as_read.assert_called_once_with("message-1")
+
+ def test_delayed_attempt_opens_no_database_session_before_sleep_finishes(self):
+ sleep_started = asyncio.Event()
+ release_sleep = asyncio.Event()
+ detached_di = Mock(spec = DI)
+ detached_repo = Mock()
+ detached_repo.claim.return_value = None
+ # noinspection PyPropertyAccess
+ detached_di.chat_message_burst_repo = detached_repo
+ self.di.clone.return_value = detached_di
+ session = MagicMock()
+
+ async def suspended_sleep(delay_s):
+ self.assertEqual(delay_s, 0.5)
+ sleep_started.set()
+ await release_sleep.wait()
+
+ async def scenario():
+ with (
+ patch(
+ "features.chat.message_burst_service.asyncio.sleep",
+ side_effect = suspended_sleep,
+ ),
+ patch(
+ "features.chat.message_burst_service.get_detached_session",
+ return_value = session,
+ ) as get_session,
+ ):
+ task = asyncio.create_task(
+ self.service.process_after_quiet_period(self._scheduled_burst()),
+ )
+ await sleep_started.wait()
+ get_session.assert_not_called()
+ release_sleep.set()
+ self.assertFalse(await task)
+ get_session.assert_called_once()
+
+ asyncio.run(scenario())
+
+ def test_obsolete_timer_noops_and_completion_schedules_waiting_messages(self):
+ first = self._scheduled_burst(message_count = 1)
+ second = self._scheduled_burst(message_count = 2)
+ first_claim = self._claim(message_count = 1)
+ second_claim = self._claim(message_count = 2)
+ detached_service = Mock(spec = MessageBurstService)
+ detached_di = Mock(spec = DI)
+ detached_repo = Mock()
+ detached_repo.claim.side_effect = [first_claim, second_claim]
+ detached_repo.finalize.side_effect = [second, None]
+ # noinspection PyPropertyAccess
+ detached_di.chat_message_burst_repo = detached_repo
+ # noinspection PyPropertyAccess
+ detached_di.message_burst_service = detached_service
+ detached_service.load_claimed_message.return_value = self.ingested
+ detached_service.process_message.return_value = True
+ self.di.clone.return_value = detached_di
+
+ with (
+ patch(
+ "features.chat.message_burst_service.asyncio.sleep",
+ new = AsyncMock(),
+ ),
+ patch(
+ "features.chat.message_burst_service.get_detached_session",
+ return_value = MagicMock(),
+ ),
+ ):
+ result = asyncio.run(self.service.process_after_quiet_period(first))
+
+ self.assertTrue(result)
+ self.assertEqual(
+ detached_service.process_message.call_args_list,
+ [
+ call(self.ingested, claim = first_claim),
+ call(self.ingested, claim = second_claim),
+ ],
+ )
+
+ def _scheduled_burst(self, message_count: int = 1) -> ScheduledChatMessageBurst:
+ return ScheduledChatMessageBurst(
+ chat_id = self.message.chat_id,
+ author_id = self.message.author_id,
+ message_count = message_count,
+ wait_seconds = 0.5,
+ )
+
+ def _claim(
+ self,
+ message_count: int,
+ is_addressed: bool = False,
+ ) -> ClaimedChatMessageBurst:
+ return ClaimedChatMessageBurst(
+ chat_id = self.message.chat_id,
+ author_id = self.message.author_id,
+ message_count = message_count,
+ last_message_sent_at = self.message.sent_at,
+ last_message_ingestion_order = self.message.ingestion_order,
+ is_addressed = is_addressed,
+ )
diff --git a/test/features/chat/whatsapp/test_whatsapp_chat_inbound_service.py b/test/features/chat/whatsapp/test_whatsapp_chat_inbound_service.py
index 2487537a..988ba8df 100644
--- a/test/features/chat/whatsapp/test_whatsapp_chat_inbound_service.py
+++ b/test/features/chat/whatsapp/test_whatsapp_chat_inbound_service.py
@@ -1,11 +1,14 @@
import unittest
from datetime import datetime, timedelta
+from itertools import count
from unittest.mock import MagicMock, Mock, patch
from db.sql_util import SQLUtil
from pydantic import SecretStr
+from sqlalchemy import Connection, event
from db.model.chat_config import ChatConfigDB
+from db.model.chat_message import ChatMessageDB
from db.model.user import UserDB
from di.di import DI
from features.chat.attachment.chat_attachment import ChatAttachment
@@ -34,6 +37,26 @@
from util.errors import InternalError
from util.functions import generate_deterministic_short_uuid
+_ingestion_order = count(1)
+
+
+def _assign_sqlite_ingestion_order(
+ _mapper,
+ connection: Connection,
+ target: ChatMessageDB,
+) -> None:
+ if connection.dialect.name != "sqlite" or target.ingestion_order is not None:
+ return
+ target.ingestion_order = next(_ingestion_order)
+
+
+def setUpModule() -> None:
+ event.listen(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
+
+def tearDownModule() -> None:
+ event.remove(ChatMessageDB, "before_insert", _assign_sqlite_ingestion_order)
+
class WhatsAppChatInboundServiceTest(unittest.TestCase):
@@ -449,6 +472,7 @@ def test_store_message_new(self):
result = self.resolver.store_message(mapped_data, formatted_text, chat.chat_id, None)
saved_message = self.sql.chat_message_repo().get(chat.chat_id, mapped_data.message_id)
+ assert result is not None
self.assertEqual(result, saved_message)
self.assertEqual(result.chat_id, chat.chat_id)
self.assertEqual(result.message_id, mapped_data.message_id)
diff --git a/test/features/chat/whatsapp/test_whatsapp_update_responder.py b/test/features/chat/whatsapp/test_whatsapp_update_responder.py
index 9ae8513b..97f6dbec 100644
--- a/test/features/chat/whatsapp/test_whatsapp_update_responder.py
+++ b/test/features/chat/whatsapp/test_whatsapp_update_responder.py
@@ -1,395 +1,239 @@
+import asyncio
import unittest
-from collections import namedtuple
-from datetime import date, datetime
-from unittest.mock import Mock, patch
+from datetime import datetime, timedelta
+from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
from uuid import UUID
-from db.sql_util import SQLUtil
-from langchain_core.messages import AIMessage
-
from db.model.chat_config import ChatConfigDB
-from db.model.user import UserDB
+from di.di import DI
from features.chat.config.chat_config import ChatConfig
from features.chat.ingested_chat_message import IngestedChatMessage
from features.chat.message.chat_message import ChatMessage
+from features.chat.message_burst import ScheduledChatMessageBurst
+from features.chat.message_burst_service import MessageBurstService
from features.chat.whatsapp.model.update import Update
-from features.chat.whatsapp.whatsapp_update_responder import respond_to_update
+from features.chat.whatsapp.whatsapp_update_responder import (
+ _ingest_update,
+ _IngressOutcome,
+ respond_to_update,
+)
from features.users.user import User
-from util.error_codes import PLATFORM_MAPPING_FAILED
-from util.errors import ServiceError
class WhatsAppUpdateResponderTest(unittest.TestCase):
- sql: SQLUtil
- update: Update
- di: Mock
- mock_sleep: Mock
-
def setUp(self):
- # create all the mocks
- self.sql = SQLUtil()
- self.update = Update(object = "whatsapp_business_account", entry = [])
-
- patcher_sleep = patch("features.chat.whatsapp.whatsapp_update_responder.sleep")
- self.addCleanup(patcher_sleep.stop)
- self.mock_sleep = patcher_sleep.start()
-
- # mock the DI container
- patcher_di = patch("features.chat.whatsapp.whatsapp_update_responder.DI")
- self.addCleanup(patcher_di.stop)
- self.di = patcher_di.start().return_value
-
- self.di.access_token_resolver.get_access_token_for_tool.return_value = "dummy_token"
-
- # patch all dependencies in the correct namespace where they are used in whatsapp_update_responder
- patcher_get_detached_session = patch("features.chat.whatsapp.whatsapp_update_responder.get_detached_session")
- self.addCleanup(patcher_get_detached_session.stop)
- self.mock_get_detached_session = patcher_get_detached_session.start()
- self.mock_get_detached_session.return_value.__enter__.return_value = self.sql.start_session()
-
- # patch the DI's chat_agent and whatsapp_bot_sdk mocks for use in tests
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
- self.di.whatsapp_bot_sdk.send_text_message = Mock()
-
- def __resolved_result(
- self,
- chat_config: ChatConfig | None = None,
- author: User | None = None,
- message: ChatMessage | None = None,
- raw_message_text: str = "Test message text",
- ):
- return Mock(
- spec = IngestedChatMessage,
- chat = chat_config or ChatConfig(
- chat_id = UUID(int = 123),
- external_id = "123",
- language_name = "English",
- language_iso_code = "en",
- title = "Test Chat",
- is_private = False,
- reply_chance_percent = 100,
- release_notifications = ChatConfigDB.ReleaseNotifications.all,
- media_mode = ChatConfigDB.MediaMode.photo,
- chat_type = ChatConfigDB.ChatType.whatsapp,
- ),
- author = author,
- message = message or Mock(
- spec = ChatMessage,
- message_id = "test-message-id",
- sent_at = datetime.now(),
- text = "Stored message text",
- ),
- raw_message_text = raw_message_text,
+ self.chat = ChatConfig(
+ chat_id = UUID(int = 10),
+ external_id = "123",
+ is_private = True,
+ reply_chance_percent = 0,
+ chat_type = ChatConfigDB.ChatType.whatsapp,
)
-
- def tearDown(self):
- self.sql.end_session()
-
- def test_successful_response(self):
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
-
- author = User(
- id = UUID(int = 1),
+ self.author = User(
+ id = UUID(int = 20),
full_name = "Test User",
- whatsapp_user_id = "1",
- connect_key = "WA-USER-KEY1",
- group = UserDB.Group.standard,
- created_at = date.today(),
+ whatsapp_user_id = "20",
)
-
- chat_config = ChatConfig(
- chat_id = UUID(int = 123),
- external_id = "123",
- language_name = "English",
- language_iso_code = "en",
- title = "Test Chat",
- is_private = False,
- reply_chance_percent = 100,
- release_notifications = ChatConfigDB.ReleaseNotifications.all,
- media_mode = ChatConfigDB.MediaMode.photo,
- chat_type = ChatConfigDB.ChatType.whatsapp,
+ self.message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-1",
+ ingestion_order = 7,
+ author_id = self.author.id,
+ sent_at = datetime(2026, 1, 2, 12, 0, 0),
+ text = "hello",
)
- resolved = self.__resolved_result(chat_config = chat_config, author = author)
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [resolved]
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.whatsapp_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
-
- self.di.domain_langchain_mapper.map_bot_message_to_storage.return_value = [
- Mock(chat_id = "123", text = "Test response"),
- ]
-
- result = respond_to_update(self.update)
-
- self.assertTrue(result)
- self.di.whatsapp_chat_inbound_service.ingest_update.assert_called_once_with(self.update)
- self.di.chat_agent.assert_called_once_with(
- trigger_message_text = "Test message text",
- trigger_message_id = "test-message-id",
- trigger_message_sent_at = resolved.message.sent_at,
- configured_tool = self.di.tool_choice_resolver.get_tool.return_value,
+ self.ingested = self._ingested(self.message, raw_text = "hello")
+ self.di = Mock(spec = DI)
+ self.burst_service = Mock(spec = MessageBurstService)
+ # noinspection PyPropertyAccess
+ self.di.message_burst_service = self.burst_service
+
+ def test_photo_and_prompt_deliveries_schedule_same_author_bursts(self):
+ photo_message = self.message
+ prompt_message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-2",
+ ingestion_order = 8,
+ author_id = self.author.id,
+ sent_at = self.message.sent_at + timedelta(milliseconds = 100),
+ text = "describe these",
)
- self.di.chat_agent.return_value.execute.assert_called_once()
- self.di.whatsapp_bot_sdk.send_text_message.assert_called_once_with(chat_config, "Test response")
- self.mock_sleep.assert_called_once_with(0.1)
- self.assertEqual(events, ["rollback", "send:Test response", "rollback", "sleep"])
-
- def test_reaction_response(self):
- self.di.chat_agent.return_value.execute.return_value = AIMessage(content = [
- {"type": "thinking", "thinking": "A reaction is appropriate"},
- {"type": "text", "text": "๐"},
- ])
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [
- self.__resolved_result(
- author = Mock(spec = User, id = UUID(int = 1)),
- message = Mock(
- spec = ChatMessage,
- message_id = "test-message-id",
- sent_at = datetime.now(),
- text = "Test message text",
- ),
+ photo = self._ingested(photo_message, raw_text = "")
+ prompt = self._ingested(prompt_message, raw_text = "describe these")
+ self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [photo, prompt]
+ first_scheduled = self._scheduled_burst(message_count = 1)
+ second_scheduled = self._scheduled_burst(message_count = 2)
+ session = MagicMock()
+ self.burst_service.record.side_effect = [first_scheduled, second_scheduled]
+ with (
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.get_detached_session",
+ return_value = session,
),
- ]
-
- result = respond_to_update(self.update)
-
- self.assertTrue(result)
- self.di.platform_bot_sdk.return_value.set_reaction.assert_called_once_with("123", "test-message-id", "๐")
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.mock_sleep.assert_not_called()
- self.di.whatsapp_bot_sdk.mark_as_read.assert_called_once_with("test-message-id")
- saved_message = self.di.chat_message_repo.save.call_args.args[0]
- self.assertIsInstance(saved_message, ChatMessage)
- self.assertEqual(saved_message.message_id, "reaction:test-message-id")
- self.assertEqual(saved_message.text, "๐")
-
- def test_reaction_response_failure(self):
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "๐")
- self.di.platform_bot_sdk.return_value.set_reaction.side_effect = Exception("Reaction failed")
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [
- self.__resolved_result(
- author = Mock(spec = User, id = UUID(int = 1)),
- message = Mock(
- spec = ChatMessage,
- message_id = "test-message-id",
- sent_at = datetime.now(),
- text = "Test message text",
- ),
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.DI",
+ return_value = self.di,
),
- ]
-
- result = respond_to_update(self.update)
+ ):
+ outcome = _ingest_update(Update(object = "whatsapp_business_account", entry = []))
- self.assertTrue(result)
- self.di.platform_bot_sdk.return_value.set_reaction.assert_called_once_with("123", "test-message-id", "๐")
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.mock_sleep.assert_not_called()
- self.di.whatsapp_bot_sdk.mark_as_read.assert_called_once_with("test-message-id")
- saved_message = self.di.chat_message_repo.save.call_args.args[0]
- self.assertIsInstance(saved_message, ChatMessage)
- self.assertEqual(saved_message.message_id, "reaction:test-message-id")
- self.assertEqual(saved_message.text, "๐")
+ self.assertEqual(outcome.scheduled_bursts, [first_scheduled, second_scheduled])
+ self.assertEqual(self.burst_service.record.call_count, 2)
+ self.assertEqual(
+ [record.kwargs["message"].author_id for record in self.burst_service.record.call_args_list],
+ [self.author.id, self.author.id],
+ )
- def test_empty_response(self):
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [
- self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1))),
+ def test_different_group_authors_schedule_independent_bursts(self):
+ self.chat.is_private = False
+ other_author = User(
+ id = UUID(int = 21),
+ full_name = "Other User",
+ whatsapp_user_id = "21",
+ )
+ other_message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-2",
+ ingestion_order = 8,
+ author_id = other_author.id,
+ sent_at = self.message.sent_at,
+ text = "other",
+ )
+ other = IngestedChatMessage(
+ chat = self.chat,
+ author = other_author,
+ message = other_message,
+ attachments = [],
+ raw_message_text = "other",
+ )
+ self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [self.ingested, other]
+ session = MagicMock()
+ self.burst_service.record.side_effect = [
+ self._scheduled_burst(author_id = self.author.id),
+ self._scheduled_burst(author_id = other_author.id),
]
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
- self.di.chat_agent.return_value.execute.return_value = Mock(content = "")
-
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_ingestion_error(self):
- raw_message = Mock(id = "raw-message-id", timestamp = "1")
- raw_message.from_ = "123"
- change = Mock(value = Mock(messages = [raw_message]))
- self.update = Mock(spec = Update, entry = [Mock(changes = [change])])
- self.di.whatsapp_chat_inbound_service.ingest_update.side_effect = Exception("Mapping error")
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error", return_value = "Mapping error"):
- self.di.domain_langchain_mapper.map_bot_message_to_storage.return_value = [
- Mock(chat_id = "123", text = "Mapping error"),
- ]
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
-
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_not_called()
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.di.whatsapp_bot_sdk.set_reaction.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_empty_update_no_messages(self):
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = []
-
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.whatsapp_chat_inbound_service.ingest_update.assert_called_once_with(self.update)
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_mapping_failure_before_text_delivery_sends_no_error_message_or_reaction(self):
- resolved = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [resolved]
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
+ with (
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.get_detached_session",
+ return_value = session,
+ ),
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.DI",
+ return_value = self.di,
+ ),
+ ):
+ _ingest_update(Update(object = "whatsapp_business_account", entry = []))
- error = ServiceError(
- message = "Mapping failed",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
+ self.assertEqual(
+ [item.kwargs["message"].author_id for item in self.burst_service.record.call_args_list],
+ [self.author.id, other_author.id],
)
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = error
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- result = respond_to_update(self.update)
- self.assertFalse(result)
- self.di.domain_langchain_mapper.map_bot_message_to_storage.assert_called_once_with(
- resolved.chat,
- self.di.chat_agent.return_value.execute.return_value,
+ def test_command_does_not_extend_conversational_burst(self):
+ command = self._ingested(self.message, raw_text = "/help")
+ prompt_message = ChatMessage(
+ chat_id = self.chat.chat_id,
+ message_id = "message-2",
+ ingestion_order = 8,
+ author_id = self.author.id,
+ sent_at = self.message.sent_at + timedelta(milliseconds = 100),
+ text = "prompt",
)
- mock_error.assert_not_called()
- self.di.whatsapp_bot_sdk.send_text_message.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- self.di.whatsapp_bot_sdk.set_reaction.assert_not_called()
- self.di.whatsapp_bot_sdk.mark_as_read.assert_not_called()
- self.mock_sleep.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_first_response_send_failure_sends_one_error_text_without_reaction_or_pre_failure_sleep(self):
- resolved = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [resolved]
- self.di.chat_agent.return_value.execute.return_value = Mock(spec = AIMessage, content = "Test response")
- self.di.chat_message_repo.get_latest_by_chat.return_value = []
+ prompt = self._ingested(prompt_message, raw_text = "prompt")
+ self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [command, prompt]
+ self.burst_service.process_message.return_value = False
+ session = MagicMock()
+ self.burst_service.record.return_value = self._scheduled_burst()
+ with (
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.get_detached_session",
+ return_value = session,
+ ),
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.DI",
+ return_value = self.di,
+ ),
+ ):
+ outcome = _ingest_update(Update(object = "whatsapp_business_account", entry = []))
- error = ServiceError(
- message = "Test error",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
+ self.burst_service.process_message.assert_called_once_with(
+ command,
+ command_only = True,
)
- ErrorMsg = namedtuple("ErrorMsg", ["chat_id", "text"])
- response = [ErrorMsg(chat_id = "123", text = "First response")]
- error_response = [ErrorMsg(chat_id = "123", text = "Error response")]
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = [response, error_response]
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
- if text == "First response":
- raise error
-
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.whatsapp_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- mock_error.return_value = "Error response"
- result = respond_to_update(self.update)
+ self.burst_service.record.assert_called_once()
+ self.assertEqual(self.burst_service.record.call_args.kwargs["message"], prompt_message)
+ self.assertEqual(len(outcome.scheduled_bursts), 1)
+
+ def test_async_responder_processes_every_scheduled_burst(self):
+ first = self._scheduled_burst(message_count = 1)
+ second = self._scheduled_burst(message_count = 2, author_id = UUID(int = 21))
+ second_di = Mock(spec = DI)
+ second_burst_service = Mock(spec = MessageBurstService)
+ # noinspection PyPropertyAccess
+ second_di.message_burst_service = second_burst_service
+ self.burst_service.process_after_quiet_period.return_value = False
+ second_burst_service.process_after_quiet_period.return_value = True
+ with (
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.asyncio.to_thread",
+ new = AsyncMock(return_value = _IngressOutcome(scheduled_bursts = [first, second])),
+ ),
+ patch(
+ "features.chat.whatsapp.whatsapp_update_responder.DI",
+ side_effect = [self.di, second_di],
+ ) as di_factory,
+ ):
+ result = asyncio.run(
+ respond_to_update(Update(object = "whatsapp_business_account", entry = [])),
+ )
- self.assertFalse(result)
- self.di.whatsapp_bot_sdk.set_reaction.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- mock_error.assert_called_once_with(str(error), emoji = "๐งจ")
+ self.assertTrue(result)
self.assertEqual(
- [call.args[1] for call in self.di.whatsapp_bot_sdk.send_text_message.call_args_list],
- ["First response", "Error response"],
+ di_factory.call_args_list,
+ [
+ call(
+ invoker_id = first.author_id.hex,
+ invoker_chat_id = first.chat_id.hex,
+ ),
+ call(
+ invoker_id = second.author_id.hex,
+ invoker_chat_id = second.chat_id.hex,
+ ),
+ ],
)
+ self.burst_service.process_after_quiet_period.assert_awaited_once()
self.assertEqual(
- events,
- ["rollback", "send:First response", "rollback", "send:Error response", "rollback", "sleep"],
+ self.burst_service.process_after_quiet_period.await_args.kwargs["scheduled"],
+ first,
)
- self.assertEqual([call.args for call in self.mock_sleep.call_args_list], [(0.1,)])
- self.di.whatsapp_bot_sdk.mark_as_read.assert_not_called()
- self.di.chat_message_repo.save.assert_not_called()
-
- def test_general_exception_after_response_delivery_started_sends_error_without_reaction(self):
- resolved_domain_data_mock = self.__resolved_result(author = Mock(spec = User, id = UUID(int = 1)))
- self.di.whatsapp_chat_inbound_service.ingest_update.return_value = [resolved_domain_data_mock]
-
- error = ServiceError(
- message = "Test error",
- error_code = PLATFORM_MAPPING_FAILED,
- http_status = 500,
- emoji = "๐งจ",
+ second_burst_service.process_after_quiet_period.assert_awaited_once()
+ self.assertEqual(
+ second_burst_service.process_after_quiet_period.await_args.kwargs["scheduled"],
+ second,
)
- ErrorMsg = namedtuple("ErrorMsg", ["chat_id", "text"])
- response = [
- ErrorMsg(chat_id = "123", text = "First response"),
- ErrorMsg(chat_id = "123", text = "Second response"),
- ]
- error_response = [ErrorMsg(chat_id = "123", text = "Error response")]
- self.di.domain_langchain_mapper.map_bot_message_to_storage.side_effect = [response, error_response]
- events = []
-
- def send_text_message(chat, text):
- events.append(f"send:{text}")
- if text == "Second response":
- raise error
- def rollback_session():
- events.append("rollback")
-
- def sleep(delay):
- events.append("sleep")
-
- self.di.whatsapp_bot_sdk.send_text_message.side_effect = send_text_message
- self.di.rollback_db_session.side_effect = rollback_session
- self.mock_sleep.side_effect = sleep
-
- with patch("features.integrations.prompt_resolvers.simple_chat_error") as mock_error:
- mock_error.return_value = "Error response"
- result = respond_to_update(self.update)
-
- self.assertFalse(result)
- self.di.whatsapp_bot_sdk.set_reaction.assert_not_called()
- self.di.platform_bot_sdk.return_value.set_reaction.assert_not_called()
- mock_error.assert_called_once_with(str(error), emoji = "๐งจ")
- self.assertEqual(
- [call.args[1] for call in self.di.whatsapp_bot_sdk.send_text_message.call_args_list],
- ["First response", "Second response", "Error response"],
+ def _ingested(
+ self,
+ message: ChatMessage,
+ raw_text: str,
+ ) -> IngestedChatMessage:
+ return IngestedChatMessage(
+ chat = self.chat,
+ author = self.author,
+ message = message,
+ attachments = [],
+ raw_message_text = raw_text,
)
- self.assertEqual([call.args for call in self.mock_sleep.call_args_list], [(0.1,), (0.1,)])
- self.assertEqual(
- events,
- [
- "rollback",
- "send:First response",
- "rollback",
- "sleep",
- "send:Second response",
- "rollback",
- "send:Error response",
- "rollback",
- "sleep",
- ],
+
+ def _scheduled_burst(
+ self,
+ message_count: int = 1,
+ author_id: UUID | None = None,
+ ) -> ScheduledChatMessageBurst:
+ return ScheduledChatMessageBurst(
+ chat_id = self.chat.chat_id,
+ author_id = author_id or self.author.id,
+ message_count = message_count,
+ wait_seconds = 0.5,
)
- self.di.chat_message_repo.save.assert_not_called()
diff --git a/test/features/cleanup/test_cleanup_service.py b/test/features/cleanup/test_cleanup_service.py
index a879fc1d..f31dab5d 100644
--- a/test/features/cleanup/test_cleanup_service.py
+++ b/test/features/cleanup/test_cleanup_service.py
@@ -11,6 +11,7 @@ def _make_service(
attachments_deleted = 0,
orphaned_attachments_deleted = 0,
messages_deleted = 0,
+ message_bursts_deleted = 0,
cache_cleared = 0,
usage_deleted = 0,
alerts_deleted = 0,
@@ -20,6 +21,7 @@ def _make_service(
di.chat_attachment_service.cleanup_old_attachments.return_value = attachments_deleted
di.chat_attachment_service.cleanup_orphaned_attachments.return_value = orphaned_attachments_deleted
di.chat_message_repo.delete_older_than.return_value = messages_deleted
+ di.chat_message_burst_repo.delete_older_than.return_value = message_bursts_deleted
di.tools_cache_repo.delete_expired.return_value = cache_cleared
di.usage_record_repo.delete_older_than.return_value = usage_deleted
di.price_alert_repo.delete_stale.return_value = alerts_deleted
@@ -32,6 +34,7 @@ def test_run_returns_all_phase_counts(self, _):
attachments_deleted = 5,
orphaned_attachments_deleted = 2,
messages_deleted = 10,
+ message_bursts_deleted = 4,
cache_cleared = 3,
usage_deleted = 7,
alerts_deleted = 2,
@@ -44,6 +47,7 @@ def test_run_returns_all_phase_counts(self, _):
self.assertEqual(result.attachments_deleted, 5)
self.assertEqual(result.orphaned_attachments_deleted, 2)
self.assertEqual(result.messages_deleted, 10)
+ self.assertEqual(result.message_bursts_deleted, 4)
self.assertEqual(result.cache_entries_cleared, 3)
self.assertEqual(result.usage_records_deleted, 7)
self.assertEqual(result.price_alerts_deleted, 2)
diff --git a/test/util/test_config.py b/test/util/test_config.py
index 2e48f3e8..dc5aa240 100644
--- a/test/util/test_config.py
+++ b/test/util/test_config.py
@@ -53,7 +53,7 @@ def test_default_config(self):
self.assertEqual(config.whatsapp_phone_number_id, "invalid")
self.assertEqual(config.whatsapp_bot_phone_number, "11234567890")
self.assertEqual(config.chat_history_depth, 30)
- self.assertEqual(config.chat_debounce_delay_s, 1.0)
+ self.assertEqual(config.chat_burst_quiet_period_s, 1.0)
self.assertEqual(config.cleanup_message_retention_days, 30)
self.assertEqual(config.cleanup_price_alert_staleness_days, 360)
self.assertEqual(config.cleanup_sponsorship_staleness_days, 30)
@@ -134,7 +134,7 @@ def test_custom_config(self):
os.environ["WHATSAPP_PHONE_NUMBER_ID"] = "9876543210"
os.environ["WHATSAPP_BOT_PHONE_NUMBER"] = "19876543210"
os.environ["CHAT_HISTORY_DEPTH"] = "10"
- os.environ["CHAT_DEBOUNCE_DELAY_S"] = "2.5"
+ os.environ["CHAT_BURST_QUIET_PERIOD_S"] = "0.75"
os.environ["CLEANUP_MESSAGE_RETENTION_DAYS"] = "60"
os.environ["CLEANUP_PRICE_ALERT_STALENESS_DAYS"] = "180"
os.environ["CLEANUP_SPONSORSHIP_STALENESS_DAYS"] = "14"
@@ -218,7 +218,7 @@ def test_custom_config(self):
self.assertEqual(config.whatsapp_phone_number_id, "9876543210")
self.assertEqual(config.whatsapp_bot_phone_number, "19876543210")
self.assertEqual(config.chat_history_depth, 10)
- self.assertEqual(config.chat_debounce_delay_s, 2.5)
+ self.assertEqual(config.chat_burst_quiet_period_s, 0.75)
self.assertEqual(config.cleanup_message_retention_days, 60)
self.assertEqual(config.cleanup_price_alert_staleness_days, 180)
self.assertEqual(config.cleanup_sponsorship_staleness_days, 14)