diff --git a/Pipfile b/Pipfile index 46a95a49..7d59ca6e 100644 --- a/Pipfile +++ b/Pipfile @@ -57,6 +57,7 @@ ruff = "*" pre-commit = "*" the-agent = {path = ".", editable = true} pytest-xdist = "*" +virtualenv = "==21.14.5" [requires] # Python 3.12: keep in sync with pyproject.toml diff --git a/Pipfile.lock b/Pipfile.lock index 568e7842..f53f8e60 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ccd0791c57ab82690d54be5405bda35d7f3ceccba8ab603db28fee4c54dad516" + "sha256": "d1f26de0e7993289afd8d06cea6a8f9139f2c602d5c4346c57cc30249b1efc57" }, "pipfile-spec": 6, "requires": { @@ -27,128 +27,170 @@ }, "aiohttp": { "hashes": [ - "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", - "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", - "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", - "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", - "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", - "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", - "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", - "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", - "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", - "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", - "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", - "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", - "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", - "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", - "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", - "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", - "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", - "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", - "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", - "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", - "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", - "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", - "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", - "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", - "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", - "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", - "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", - "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", - "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", - "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", - "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", - "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", - "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", - "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", - "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", - "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", - "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", - "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", - "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", - "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", - "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", - "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", - "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", - "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", - "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", - "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", - "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", - "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", - "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", - "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", - "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", - "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", - "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", - "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", - "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", - "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", - "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", - "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", - "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", - "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", - "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", - "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", - "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", - "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", - "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", - "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", - "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", - "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", - "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", - "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", - "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", - "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", - "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", - "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", - "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", - "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", - "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", - "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", - "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", - "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", - "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", - "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", - "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", - "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", - "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", - "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", - "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", - "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", - "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", - "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", - "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", - "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", - "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", - "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", - "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", - "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", - "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", - "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", - "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", - "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", - "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", - "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", - "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", - "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", - "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", - "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", - "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", - "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", - "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", - "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", - "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", - "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", - "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", - "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", - "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", - "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", - "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", - "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", - "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5" + "sha256:0133c3c3b54a0bf1e71fa5c1ad95c93f07fd54e24ef1fe182f5122e1573d2bf1", + "sha256:038c2c7e8caa26b6c8423779b5eaf1893904048a512c19b32fe841ffa5592b50", + "sha256:0684952aeae1f5dbfe02d46039338513b94009baecd15d8e4098a357c4c4a2a6", + "sha256:0790ec66fa4013e83c53b9025a45d454723da1a2fce28b3208c9b32d08af162f", + "sha256:083673c7a94c3ea035caaa5ca04288bdb44887abfe1f5ba23294e6a4b03efd2d", + "sha256:09e0eb18c7e0c8777e2f9149de63799195b9b3ca1b5c81ba6f32f2c6b8628210", + "sha256:09ec102b4b8c9a920275733bbc11fdbb615efe6f9231a06007c0218d336fb77a", + "sha256:0a8ea271867e360ac985ae607f4a23ad9a38414b9aca1d49ec98839ae660e49f", + "sha256:0e6f16f5e49c4b8267988c05ab07760d7064cea57d077c3d068d04b0fbb992cb", + "sha256:1061b364556e8172e8d46b0b183adeeb73e8c42d30ebc745591e1bd89acad52e", + "sha256:1220353657ad49493551f089ce02f1a348fd57ffd585bfec77f2f3c4fe3a7346", + "sha256:137351bf20bbed9a65e839f4a4452ac377389bdb2f2857d2acffef38f5e9f2d1", + "sha256:149fb56caf7acb67073126f675d0958d9c4b3125fcd3f6d4877df98aa8a97ce9", + "sha256:14f04769cfefe4734016a856a83af36133cd17779cef9ae817f812b8ba9d6d51", + "sha256:15a310d3c71398e3d7bfc93a1a73fbe664315cd9e9016b8efc1cff85eeab7155", + "sha256:1612fa5857b37bf32e5c1eaeefb96e3b01e9c70679eec81f0934e8a600080863", + "sha256:16c8abd5bca220a47efe667d26f8460124c81810787e79ee87b242677563d9dd", + "sha256:19e2ba471507c34f8252402ab50f5ab512398b9ea8c8f1cb26beb3f75793ba30", + "sha256:1aead151c3abbac6b32942e452020cb66d7efc099d253cc6c20f748e926c858b", + "sha256:1b438b73c38111818d0c9d6a5c2bfed8584c8e503a49ef085d70e874ec846738", + "sha256:1b5416552740edf07234cc9437d0706f2acb67b93c198670b1a68e1b2b587dec", + "sha256:1d2d981b53dd09a319e3570ef8cc3bbc3ef86f5a7abef0f6b2bff3867db3a9e7", + "sha256:1eb8167961ec4dfcc8cb9dd50bd0ee72519f7ef496be95203e49e27b01618382", + "sha256:1ffa3a523a36d8628f98c06492ae16a31a23d14c0b4ec721757b477319f656d6", + "sha256:20064a177a070d789ee64a50b01a9161d3468e989baacfc6c714aa685c4b332f", + "sha256:20726f9782d5c2744c1c66255842d1d163bb3edcf768b8de25216bf47f7b6ccf", + "sha256:20f085697d7e911f1f73c43ed03fafbed1e7121797e2eb5428efa80398060584", + "sha256:225c579c23b68b343cccea27a7e06e3bd8ec23a09c30b427eb3f1e4ca6239b20", + "sha256:24409db442e2fb6e766bc7f3943851a8381dec3098140e43bb2e843b79e31b12", + "sha256:248d779ad720b49d4fb355720e60c9e5f444f95887bc16974fea48fc56c41789", + "sha256:2528cb4c6b92008c76ac9ac6298624069bb2db91ff4929905512d1d84485f658", + "sha256:276a4fc00b1d9ae492b802763a789c5b86328b989c5ea169f2faa447d6a11c7c", + "sha256:27c2322e03f66101acb09869ce1cf1efc04994ee95e1735b69827bf8c8b9d781", + "sha256:293d3ae7c6a0ed176a42e59a1b5fde825ead65c835360f734148e96729f928d2", + "sha256:2cc38a4f2b516bef1714e690df87a0e043faf1a7693c82d860091684453d5111", + "sha256:3093b72c215bda16ce961a6d073f6e71d46e022962a9d5d457c5d4d421c78b57", + "sha256:32e8fa6644e541fcd7e02430588c7fc93b602c1778ea0bc345505db76b61cfb4", + "sha256:330900acd0dc4cb8b27f9c127fbaad770964845338493e7906ae3822e82dbf8d", + "sha256:33f706574e32c6e694f352a856e05caf18f7f2c871b3e87b41c55ea452b409ab", + "sha256:3ae800a20947e2c2e53088047d021e6bf7d51560cc49f6a0737a1f79d2e3a13c", + "sha256:3be7dd397d64ca3e1869626fa9318aaebb54b7bf93bc72d7a205448d83e4f748", + "sha256:3e0eb43bed3c6801a6cee315195377789e90b2a72c2277a475b578535312488d", + "sha256:3e51a27980c3788e6e6b3325d694fdd4898087fa8a86b2763af77b39353da41e", + "sha256:3edbece0379b8b4aaa67619b8aa2399bb66fce372cd5911098a434ea77220aa0", + "sha256:3f2dcc00191fd563e9075181a14ec31d7dd63223ced7582cc70a15a499de0c79", + "sha256:42b5e616946dbaf505e2bff18c9af2cd4ef9e7ef300ee58a6e951a5b7cf147ae", + "sha256:42f320d4a5b00b9af0bddcfec5407dc6f2d9816f006b2f79ebbaa31f16895df3", + "sha256:43351bdb5e4c3cb7d1772368e988534e869a74db7778079a83782c11c69535c7", + "sha256:43e1b7994a8b038125f722bff07492ef501110722c2727c408995d9fb864c421", + "sha256:4887d130a7bbfed3a85493bb5a25e5b5b558d40c1d986dd16970d2bb26d63793", + "sha256:4f07fe3ac408d8b3f768be471dc3f56d43843c47d97c66120534467a15ead197", + "sha256:50343c1757b4b6f6708eeaf24534b32f19dfb99fb1b762c00420867a62fc81e0", + "sha256:50983e3be33d8c0942ab88cec3905b10602f64c469b20153c48c5d4e558dd016", + "sha256:50a195903119008fe9cc68710535eb37f556ffffd6a7759afe70a2c145587045", + "sha256:5558a7f5a05af9ecf744af91e5baefc436f93c9333e656c27ec253f9a6bbe178", + "sha256:56572c42e3ecd636de8d2c3dd54cf5fc939cb5c32eb56297f176a0d366fac622", + "sha256:56d9828f204331a5ca8850fcfe2bcce95a149f1f223f60cc7216e5524978e480", + "sha256:579f97d5120f2971876d2ddca2968135f6944d00c44c3a6590ad7d86ca9b403f", + "sha256:5af42135fdfebdadbc2bcd9c0842a48ccf0d62794c36a260b21dc4b94d1e0119", + "sha256:5ba14a839fbe87cf7c12a6b5661c05f324a296eb8363141edb3944ba63d4c9d3", + "sha256:5c76f1802bab718a68ac3cce447160605c734551f95c67ae90fa1132b215cb29", + "sha256:5e8f97c0488ffda3082766ac0f2c8150a9a58c4d05788330e479cfd449b37939", + "sha256:5f3e96071686755d9cd3600c3880183eb94b012178e92746d68101800f0ed8a3", + "sha256:5fb6a6e919bfb703227bc1ce6579281b84b1a2ba57deb9794dfdbec7dcd1e40c", + "sha256:602c1e9b718a3275c580149f947e7fac65044c0a20e599553fb12e9700da9eca", + "sha256:657291433bf4dd3142f3abac495764cd47d0c7c92087751e6666c6447e65fcef", + "sha256:6774814fd5c338e72ee0da5cbb9432816df450e69c019f72b5d29bdec2a1792d", + "sha256:6da32b5ff3fd78d244e37300463434c7145162bfd2b6e9e915ab164da37f7343", + "sha256:6e1d8637cf73eebc92eba2e11d4cfff98a3b562f2505bd75bba766d908926e8d", + "sha256:6e4251c0ba4624a68a2c11471a1ac54c3306876c21f0ae86de085cc9241c8905", + "sha256:6f275c11d1aa6d4c458e05a68be084efe3c55a113d99e3f46a318098e52948fc", + "sha256:6f967dde489ca6a8c02d093ab245d2cbf50ccb5c36adf0188b17b0ca39d24b67", + "sha256:6fdcd6af7e2e51d1ba1b4bea16e97b074bcb7b5dd0246a9d8201341bb28085a0", + "sha256:70cb4008ac2ed1e0ca9e824deb4b53d3aa0d939109698ebf1e723a84337bd794", + "sha256:7457580535e019e1247ea35d6a02bf081ad30c26d0cbc210c93f6c3ab67a0835", + "sha256:74b0a9c8270f9b0a11410e124ff8d4f18bfc1f1837440ec84da5ae7b50927b5d", + "sha256:74efb69332b85675b1eabd760a8cfc2e2cf42c60607c66f88014c1bdfb40942d", + "sha256:755933b107ea7a6a9ac916f635a70595a5b1a32fac10a8ff0b9f2ab88555550c", + "sha256:7779cd97e61ebe583ec2f1c5616cdd038aa08a4453b1848c67842176d054948e", + "sha256:788ecaa9c10533b786ce5ba70c4f2df78ad41819fd00a6c99d92b66f9a32e1da", + "sha256:7bd8ac754ebd6733a3e2a0dd1674c4d8ab086196803fd8dcd776f07b4e2607d9", + "sha256:81c2b3dfd56c62bee6108e4852d5970b4cf9086390b6983f52b666e878c1f115", + "sha256:823c910f046f23f4c713b8d99a2242dc65f591cb45ee86418fa11762a3c2963c", + "sha256:82c7583cd3dfdc7dcc927835b4f6c7faae7ecc1ba3ca5879321621ae2e6f8e84", + "sha256:8966ecac808dd5f473c9c4cefd10cd3ffda71c18a4d3493b7c7d2ae1803bf2cc", + "sha256:8df7d481654ac96fe1ba9a02a9f67770fdd367823e0d5ef01b922725c4bd2cfa", + "sha256:8e317e0fb6b16212c881d2205a7d87414c29acd69320b3aa6dce9d9c7b86fe4f", + "sha256:932ce7e694bbc29b2bf6f64f2343c27d148d4997c771d01bdade4639b6749ff4", + "sha256:939042d5cda21d41a6f512e7cc8b8e33a2aebff863352251da495fbd91b673b5", + "sha256:94684b879ac1d71e4238850c99b62dc1b28d9086b156a2555f082010b85a865c", + "sha256:96a2e584f0b9ed8f1fa33211397dcf67bb7069866402cb405d191c2f0defb9a3", + "sha256:9ad7e6aa38c20da1be697874349c4c273c8a03b7887169665081706398d0439a", + "sha256:9b42db919715e91eb76acf3bc492a9a7ccd8bd9adc6745c1412b689735269f14", + "sha256:9bab2045550c4fe0f7baf89574db1b455c195750702ba96fef1f16972b146617", + "sha256:9bf1d5dcc15204d9ec8b8ea4c18fd66e6b80e5de1f4ecbafb3a2f2740f8039d4", + "sha256:9c061aa954daaf57d2a4b8374f9fca621ef0e1b603584431c220c22458c59b6d", + "sha256:9c428eb2bd8817588d16a0ab898aa4eb5d141f896aa2b394cc79a4cf61d9a8e2", + "sha256:9cc882cf8619109583c906b4d4a85d6a111a98afa34b7a450d1e08118d016820", + "sha256:9ce66feae6ac65327379460380549bf1b8df8e17c4e25df2a2bcf168272e3bed", + "sha256:a23fe35d776bc03cb495938b9594450d047e3bc08c5255315a82323e9cb7d2dd", + "sha256:a2c473a355f9239efcb72c92d5abfd8fcdb0cc78c8e9af607e72ca12dbb36593", + "sha256:a63afd1f757de949028387e65a7127b61ad0f775432dbb0e62816ae619fe69ac", + "sha256:a6d02b4c38de03d9c7617813433e6a0fb6b522797974177d69d9dad431900833", + "sha256:a7d470cf7b206e6359fc77b1b860632fde400d5a2ed59cd0181b93a686bc81ee", + "sha256:a95529a92a446db351675f4aab518feaf5e99842f63f5dd17160c2b74f382db3", + "sha256:a9918e58faf62ba2c7147927d06057aec78f42475aff5048047ec47e7265a600", + "sha256:a9d3983bd6ab7aa1cfd573544ae98df9b6cb6912a5185a198263e024a636861d", + "sha256:ab52d8f1fc1b64821c1fbad64a647ed6203627004059a6d1ed4f0858a1499703", + "sha256:abfda5cb094a829f7bc25216a32f7db2e85cc65bd59910f8e7b40b3d9b224764", + "sha256:adbeee7d6fd4cf5fe0aece2fb3edc4243615d3180430ba8149d01a90670cac99", + "sha256:ae53924aa853a7a2ca20ed4142c7c6b56338e4d4cd999e2980075b9efc2e257a", + "sha256:b032a0023eb41d768ce77d83210ab2a3c389bc0b09313273c7e1eca48c10a755", + "sha256:b1b8ece1e71132d2afba4dbc0c3d62c766e25165990b25db1196c04969eb3d84", + "sha256:b2966998927d7bed9db12c0a4647b0c7b179755878fc9c357fe1ffd3e3b0c1a5", + "sha256:b3cc509327c7b27f6f4727a8830f4004f6df7766e179f2f4b8e54e65c0bec5d3", + "sha256:b7806e804889231b0e06469fd4a5c06313d1c0a3377322b6d9237fa5e0fe4167", + "sha256:bea559ad70218d230663e4210875735076a9bfea5994cef34a55a25faeaf2544", + "sha256:bf163cc701f3d4ac43ba7d97771bf5fd955220ef5500ef3ee847bc0ecfbf4ec1", + "sha256:c147451b4a58e7050f7f7394e6c467867c84161560001f9ad4fb2d1446743946", + "sha256:c172db893e516e1358e65a95ee20b7ce7173963eefe318b6ab2a2220688b999e", + "sha256:c1d60eafd9c7e8e74abd03a5b00df44e7febfe6d9b89b559c0a6551eef0699d4", + "sha256:c2c30484dd1417ef98b51021ffa2cc0d7f3c78918adaaaab7e70817335ab3e02", + "sha256:c32e26310cc10e547f53cd13d39a369034f69dcb7d749d5cb0e5f67bc196b6ba", + "sha256:c5ed596aedb9c42afd3fe0aae3117725378ac73d2cc5ddc735056fbdb96c5d02", + "sha256:c8859a013ae0de1074660992139a1a440df3e6b219b86cf0d3f11c2692bb4fe3", + "sha256:c8c4478bef6d57fcfda15dae461ea3c9f06aa7b257c58df3f2300174ccbb185a", + "sha256:cb11a971a3aea10f9b8373be628f1df932964fc6c6b174516d318a48c3ac4412", + "sha256:cb131d775a1573c1aee66656bd78b023577bbdb6cb8349a07773bd4f73e68a6e", + "sha256:cd88b01f3d37b7a2a34f91d98f14720206f1ea3d540843fab2d649dd5fb91fec", + "sha256:d05e94cdfe0d15d0206f970722d2554780ce562787b21b218b275447f8751319", + "sha256:d079c0a0135c36e7beb6f1c88087c8f108dc5891cdd0b5eafa778421bda70ed2", + "sha256:d3112585250b199296c26ca6e0131640b6a8d01bab8b232d2eb3763ed469de11", + "sha256:d418ce2af40c6bb685b3f663e9e8de27cb0a22431d8e88a167348d7f01878073", + "sha256:d51db97c96384fbfcaf8f4c65922183a68b94f891c3c10c862ef5f6df2adbb1f", + "sha256:d94e44be379e569758fee8a9a58431cfc3c2598c708b92b1cfe96c66b4c94aef", + "sha256:dab9ac5a67c8d1f070c00fa8fccb7cbd1b8dcc1a8d6b42f37540df9b3d4cc603", + "sha256:dbf53ae2601b7fd5a93c3944deea3a78d40f495226d582c35ef7a433425ce2b2", + "sha256:dca3fa8d8a0a26679862eccb0b1a9151b2b9f1cd2c212e7a6335778faaff5833", + "sha256:df37b620684e19b5e25724412518ccafc3b1a49cdac706fdbd2f983fad943450", + "sha256:e1cc2bfaee8c214f06080a7c7d5772419b8a1108e8e5349236189811823fb02a", + "sha256:e29347c142cf6e99e0dff5e2995ead1d50fa3b51bf37a7c726a7ccfe5419745a", + "sha256:e4f5cf4dc72a71c4cfa9751b4950be22f733626670230d46e7d606592aa22d59", + "sha256:e724a7b6091f0b1ac064f9d1b15ff9ec52e6033a86cdae649e5f086e32a3c0db", + "sha256:e87046c8ff77a8decdb6a41d8ab25824b47531b2da933aeab0c1e21c7acff329", + "sha256:e95c8def4b81c5d68d5cf1f54c07acd7c0d2577af244e5b6da802120825737c6", + "sha256:eb324e2009fb54db30a071dad7caf6998ee2879c4704007efb244514dad1fec1", + "sha256:ef60869969180ec2464f1349aff07138ae35ca2200f0946cb3552e49e8f301a8", + "sha256:ef692a24087a699c0a4a26af45e746e0c1eae2116f6d8a5ff91d8aae2b867b45", + "sha256:efc21a454892828368b11c2c780de0ff8bc991f73f6b99c6b66e56205470929b", + "sha256:f001b571ead90ca1770f1e616db255351a1703317f20374c361ef22f12c06d09", + "sha256:f2a7966bda23dd85051f1661ce0ace38d6890e05ec6c357ecae9d2479cba377e", + "sha256:f2ebb54b3f932210503072f09974b4fb574d823e497a944adfdcd140a6a00255", + "sha256:f2ed8b64dc0c651c0f5a9c926777719770021251b8f336d97c4b80b660836ce1", + "sha256:f375db73a39f5cf83696d500e21a67f418dc9a988955756f254be8f03b7b3651", + "sha256:f59c7673465908cbe506117176156c127f29f917677afceada34957179221d91", + "sha256:f8d40ce41991e9d56fab4f5dc4a51fe59bc3b5c77c27f4b148963064d00232e8", + "sha256:f9033b43f511f27547c557dcaba0177649e10a3725336ccd2cce0fdc1dc4850d", + "sha256:fba47bc2c3d7303c3d027c6cf4d07626c37b1314ac81f5820c31032e0ca1f677", + "sha256:fbdc5ec49f9ca3cd24955cf3520b10a4d4c901ba2572094c84274e9e7eb30534", + "sha256:fce9523df31cea6284f3e2c479876750d7687cf671d7b25d32b19effc0e86441", + "sha256:ff75a7537413a86e7cafe98e0e1d6e3dc4b15c6349896e7d5c6b881bfdb6d550" ], "markers": "python_version >= '3.10'", - "version": "==3.14.3" + "version": "==3.14.5" }, "aiosignal": { "hashes": [ @@ -186,11 +228,11 @@ }, "anthropic": { "hashes": [ - "sha256:3906fabac7ad7b5b46c6186040398fc7826885c77ce34e4dd7849de16fc8d0f8", - "sha256:52f97b2c485cca7ac66058374f5073a3febb7e6849b16989c145d602a3efee21" + "sha256:157bd74dbf6a595cf9e5fcac557c791fd297924dc48261f57470c2ed6f4e6671", + "sha256:ad11d9bb9adafdfea26113943bcde9973e2a439ebeeda6c89ff3e3d85bb2f5c1" ], "markers": "python_version >= '3.10'", - "version": "==1.11.0" + "version": "==1.13.0" }, "anyio": { "hashes": [ @@ -226,20 +268,20 @@ }, "boto3": { "hashes": [ - "sha256:c11ad4c429a983493ba10014c7af9831a455c2c0eea91c1cefff74530e480277", - "sha256:fdf2e304f9e8864b8613591b18b36ee995560c9666033622eda1cb9057866880" + "sha256:5ae342a16c848909cd42d4be404f69d9082e5705460198d4d3327eca5f6cddcb", + "sha256:c79994619c8d89e45f6fd0edc5c5b5a70c9358f00423f4c99cb64931f89ecf37" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==1.43.106" + "version": "==1.43.111" }, "botocore": { "hashes": [ - "sha256:006870b3b4e40547232ad12c3bb4faec91bbbe0659aafaa3b7fa48a112c4ee97", - "sha256:c1fb8818f9957cb5037f73db34ac1a12ba43562e31d6e7f9cb7e5fc64e1dba78" + "sha256:44d5e80962ac6cb9e85af72667b77c9586451e3328ab0ce33195380767e213d8", + "sha256:f1f4c28cb2a096bf246d0bb24cbb1a01c5cb696ef499fa71b155adda7b94c90b" ], "markers": "python_version >= '3.10'", - "version": "==1.43.106" + "version": "==1.43.111" }, "certifi": { "hashes": [ @@ -617,11 +659,11 @@ }, "dnspython": { "hashes": [ - "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", - "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f" + "sha256:9a4aedb833c3c1b49214d04d44d3032ab7a9135f7c1d29a549b4ff78fd82fda9", + "sha256:b44dc6b18f07a8b1c56676a19fbfdb5209415b046a9cece286baafa87ff3f7f1" ], - "markers": "python_version >= '3.10'", - "version": "==2.8.0" + "markers": "python_version >= '3.11'", + "version": "==2.9.0" }, "docstring-parser": { "hashes": [ @@ -649,12 +691,12 @@ }, "fastapi": { "hashes": [ - "sha256:06366626f2e70576367714d9ab2fe8472e6c8456dba69b399f9f797ab5e92570", - "sha256:bd5f4d81f1e93a88bcd77caf4dfe3c2dbffc3805407a0007e9a114c18b3a670b" + "sha256:1acffe48206a80917cf7dac21992b5c44b25384e8902bf745c1fd9dabcf6c51f", + "sha256:3e9395fd35276425b61b516a31fdd7c77fe2af83e41b4da22e30696fb1304c5d" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.142.2" + "version": "==0.143.0" }, "filetype": { "hashes": [ @@ -804,20 +846,20 @@ "requests" ], "hashes": [ - "sha256:89c3f931683a482ac97e61df7eb9da5e08a91703f3c752b2377d72cfb7d69e6a", - "sha256:ce50fc533ac02f489a2b183a0c156672c376ecb2091b1127bc7efba2975fff27" + "sha256:37f0815967322e8c32b12bf422531e8b637cafdaae0acbb9141117cfe6a96f23", + "sha256:ca60266a37475ae68b63bac007272f46094b3d571abe92aded329b6cfb568025" ], "markers": "python_version >= '3.10'", - "version": "==2.59.1" + "version": "==2.61.0" }, "google-genai": { "hashes": [ - "sha256:77bb207d535fc84934b42f95db35267df40fea7244b824e91daf2e7940358db3", - "sha256:a9d963900d12c6dee8fb730c87c64d06b8a29c1e3d649137d4b89560c3424776" + "sha256:124d3eef9ff6b9e4ea548f95ff5b7721683e7990c0a936b006d9e2ab61ea897d", + "sha256:7adda55cb6746fb46d38ad3202c3372840b96eede964b5fb3df1469cd4998570" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.26.0" + "version": "==2.29.0" }, "googleapis-common-protos": { "hashes": [ @@ -971,12 +1013,12 @@ }, "iniconfig": { "hashes": [ - "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", - "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", + "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.3.0" + "version": "==2.3.1" }, "jiter": { "hashes": [ @@ -1113,28 +1155,28 @@ }, "jsonpatch": { "hashes": [ - "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", - "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c" + "sha256:417e05303ebf7aef98d3ebf1e1ae7e7a4de6ec57bc5d243cd3509eff650e959f", + "sha256:679ad08672b4663c7ef1e5f3331d940f5e7786661b9acc1530104be1638e7a4f" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.33" + "markers": "python_version >= '3.10'", + "version": "==1.35" }, "jsonpointer": { "hashes": [ - "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", - "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca" + "sha256:47c846513b3a4ec46eecef1105207fba075e2a3659048e362bd7daff0fc33342", + "sha256:b19ee68644e9ffb51440448d8f7811af2b7406eea1db90603e93f5849323119a" ], "markers": "python_version >= '3.10'", - "version": "==3.1.1" + "version": "==3.2.1" }, "langchain": { "hashes": [ - "sha256:0334066939dd1c7551889727ed2d09a72dea7a6aed2193ffb61817fb658d8921", - "sha256:aed5c1a7cd581f70ddc076b75fdbcbae8031b8373983f86efd287aa70798abc2" + "sha256:2fd27e9a2d54ebcd915ac5ea51d79c772e020567ef6c6a76678182055489b804", + "sha256:baac6e13c08460e1decd0e60061b9a87c07135f1c977424040b0b57ff7bc7887" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.4.3" + "version": "==1.4.4" }, "langchain-anthropic": { "hashes": [ @@ -1164,30 +1206,30 @@ }, "langchain-core": { "hashes": [ - "sha256:67616d984c085009be2c381c2efd330d32429a58aebab295b4f51b6573964511", - "sha256:d9a71e312ab7443701238750e04a57aefc037c4f1ec82837c8a1609d9a14a9b1" + "sha256:2bacea12270fd4cfdeeefdc5977e7e8b3c30814c509534d1ef43f4e5b7d3f657", + "sha256:34394228603e6f0bd24ce4e773b58b7d160e1b8cf489ecb350cf41f5366f5e92" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.6.6" + "version": "==1.6.9" }, "langchain-google-genai": { "hashes": [ - "sha256:7871beec56ac07b719f77c46997845db6ff2267b817bffb0f97877053b0895d7", - "sha256:8e23a1307bd2158590bbf9d99f1d658fc84a9d9ddb77fb1b372f8875a2bafbbf" + "sha256:1729b83e1e08d1431a6f4f29776b6b787b4f1c2ca95a248b08c4d90149d817bd", + "sha256:462f1ad309498d11c8950d7b7ccf943580b021c0d778bd1415fa72e46e37e762" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==4.4.0" + "version": "==4.4.1" }, "langchain-openai": { "hashes": [ - "sha256:b276a01b43c5287382f62070a4e1ca5bad5581fadab8c086e46b79daa4bf975d", - "sha256:e1b9b318fafc47b2dbc3485778aa23f809195065ee313611c188af29744b8e8e" + "sha256:7df2db16d32320ba563b7e4291a71ec6bf7b0ea8a5369ed2a0501d74928ea6d0", + "sha256:a4d319385019c010c579b39e8951e4d5ededeacf94cd9c79162f4cb7ecba50fb" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.6.7" + "version": "==1.7.0" }, "langchain-perplexity": { "hashes": [ @@ -1208,11 +1250,11 @@ }, "langchain-text-splitters": { "hashes": [ - "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", - "sha256:a2de0d799ff31886429fd6e2e0032df275b60ec817c19059a7b46181cc1c2f10" + "sha256:50edeb318b3be6a6308dddd7d616ab82823a9ccbea59a9c14c58a7b91df60295", + "sha256:929b6c76f99d611a5b1d8f5591ef302909b86a436ceb3217034d24e4757868d8" ], "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.1.2" + "version": "==1.1.3" }, "langchain-xai": { "hashes": [ @@ -1225,11 +1267,11 @@ }, "langgraph": { "hashes": [ - "sha256:5fae9fa4ce771e90f10ff19ce0d5aaef4b41d7632c713d769e89cbce1241a4b9", - "sha256:95403af7b510de8d79164742f71daaf866c69ca804a8cb72bef2cc1fa1ab9813" + "sha256:5b1a72b7a7f994ac6947b7751fbbe00e949f8bb44d9ab78881604b703247ae55", + "sha256:e3f5f8ff4259f9281b73ff50fc9ef5cc3b025b8936b8283ecdca7cc0c4128690" ], "markers": "python_version >= '3.10'", - "version": "==1.2.12" + "version": "==1.2.14" }, "langgraph-checkpoint": { "hashes": [ @@ -1249,19 +1291,19 @@ }, "langgraph-sdk": { "hashes": [ - "sha256:d49a98a2ee8e0c494b101a7caf8061c8b4b94d3ee5ada0ea2dbe50903a5487b1", - "sha256:e03ef033a20d21288af496e9b6c08ae3afcc1b58dc5abcd37575bfd1a93045de" + "sha256:57122b02bda41d37a97a5e09eb018aa8c9b3dc85c433880073779bd9324b7eea", + "sha256:ea5b1749d70987d741c9b9fe2b7e1c5f7da7e4d102f64e390333c1890c9bbd5b" ], "markers": "python_version >= '3.10'", - "version": "==0.4.5" + "version": "==0.4.6" }, "langsmith": { "hashes": [ - "sha256:2df33ebe2ff20461b1f818895b8f15cbfd6e5b95be9d9e754ab3045cc6fe6f53", - "sha256:9e3dda3df013514b919acf3b147fcecc1758b8ef04a764b6cb61052991617ee2" + "sha256:10ead0e0134ae2ee49371091e9d996e7f32a6103bc35860d55c26ee42d975bee", + "sha256:748538d163cd073879106837d7f0892f34849655ba43cbb5aa5e21509e24423d" ], "markers": "python_version >= '3.10'", - "version": "==0.14.2" + "version": "==0.14.7" }, "lxml": { "hashes": [ @@ -1455,597 +1497,653 @@ }, "markupsafe": { "hashes": [ - "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", - "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", - "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", - "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", - "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", - "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", - "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", - "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", - "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", - "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", - "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", - "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", - "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", - "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", - "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", - "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", - "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", - "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", - "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", - "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", - "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", - "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", - "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", - "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", - "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", - "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", - "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", - "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", - "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", - "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", - "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", - "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", - "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", - "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", - "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", - "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", - "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", - "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", - "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", - "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", - "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", - "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", - "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", - "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", - "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", - "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", - "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", - "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", - "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", - "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", - "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", - "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", - "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", - "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", - "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", - "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", - "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", - "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", - "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", - "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", - "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", - "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", - "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", - "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", - "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", - "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", - "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", - "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", - "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", - "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", - "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", - "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", - "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", - "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", - "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", - "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", - "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", - "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", - "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", - "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", - "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", - "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", - "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", - "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", - "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", - "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", - "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", - "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", - "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" + "sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98", + "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", + "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", + "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", + "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", + "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", + "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", + "sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a", + "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", + "sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f", + "sha256:0cee7cb0f9a1b6892ea482237d9403b3d1b4603aee057d0ff01f0fac2d019a97", + "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", + "sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7", + "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", + "sha256:14bd2d845d62ab678eaf81da89d7b621b51756c72346745c1a594c09d49207a2", + "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", + "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", + "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", + "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", + "sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df", + "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", + "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", + "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", + "sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc", + "sha256:2b2b1e18af909b448bb3cf9e3433366f7a8726271fc214e8b10e0f62a78c724b", + "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", + "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", + "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", + "sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5", + "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", + "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", + "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", + "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", + "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", + "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", + "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", + "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", + "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", + "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", + "sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf", + "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", + "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", + "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", + "sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278", + "sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d", + "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", + "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", + "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", + "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", + "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", + "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", + "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", + "sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7", + "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", + "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", + "sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e", + "sha256:57f9947a7e57a081c1e3e0a2dd0d2dcf290a4531450e6f611e30084c222a7295", + "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", + "sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7", + "sha256:5e8b3d0b18fd623afa12ecb2ce8d8becef69f9b5440c6330c7972200e0bb84b0", + "sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6", + "sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed", + "sha256:6669c1bf34080161ce49c589cc512ef24d4c704ac9d2b2d3667f519c60418378", + "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", + "sha256:6768d67d1bce64270e0fdc2e69309d68b9b18ae56ddf6c711d168e9d051c2cac", + "sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b", + "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", + "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", + "sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808", + "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", + "sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb", + "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", + "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", + "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", + "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", + "sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91", + "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", + "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", + "sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e", + "sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707", + "sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21", + "sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef", + "sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be", + "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", + "sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a", + "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", + "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", + "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", + "sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581", + "sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692", + "sha256:9240187afb63d2f9ddc3e032c670356fe941f6e20662ea168a5dc3f1f317e1b3", + "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", + "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", + "sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36", + "sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9", + "sha256:94e4c421742086aeee4c32a506eec8859d7634aad943f7e6aacf70f813478768", + "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", + "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", + "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", + "sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346", + "sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c", + "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", + "sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9", + "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", + "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", + "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", + "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", + "sha256:ac0c7c9f1609b0c4c114feb1d7a3409564c7fb77e360bed9e97e5d25dfeaf868", + "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", + "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", + "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", + "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", + "sha256:b61687d0828e72bf5cda24a2690188f37170bd31c9359ac97e4e66569f120a16", + "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", + "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", + "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", + "sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c", + "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", + "sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169", + "sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa", + "sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77", + "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", + "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", + "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", + "sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e", + "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", + "sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a", + "sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9", + "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", + "sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749", + "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", + "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", + "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", + "sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889", + "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", + "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", + "sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2", + "sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786", + "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", + "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", + "sha256:e841068dc0be4cb6dfb5c890eb88cbdcff2f4a332393c7ec94e8e618bd32c1a8", + "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", + "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", + "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", + "sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237", + "sha256:f291bcf42ae98eb5107edb162c3c998b4a89648fd8e99ed4cbd12705292788cd", + "sha256:f61efe1d2fe0de16158a5fe1d1cf3c14bdb6aecd54d8938fd26512c525c1f624", + "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", + "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", + "sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8", + "sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3" ], "markers": "python_version >= '3.9'", - "version": "==3.0.3" + "version": "==3.0.4" }, "multidict": { "hashes": [ - "sha256:006c4478de0a1876f4834e14255776286f09b9846b505fe63f67f9d173a9487c", - "sha256:024123f0ab402ab33828e24eb80fa8f25167d0d3783ba5f287e39ed741e6abf9", - "sha256:02f6d0c4b70f783305e73f9944d8efe6be1022550f0974ba0ae9d8893c0350fa", - "sha256:02fe09dc197b8ae7e355371e51e5dce2f39060cd5a8badf94904527e23a3f188", - "sha256:03731f6fc036180700c9dc2308205a48e5ca6f3ff03087739ab746f294022201", - "sha256:03d47df72f084f757c1cb771188d5f4e3a805e4abc4d67e32509272343ae9382", - "sha256:03fac50ddfd8302175b77863a015eccfae76767cda5506eef86df559ba861e1f", - "sha256:042fb0196047e786936a730bd302de83143950da45f2c16078da8f35e1cf7919", - "sha256:083735b7f395894e43adb278d5dae901448883a835ff8f1977e285fefdb10418", - "sha256:095d900c242e00fbe5f321ee072e7278b4153e78c5ce9c1efde167d62c1e4771", - "sha256:0a5769559e3312dd96731fbe15b4abb6033368ac1cad5a98dadd21946a4c7d6c", - "sha256:0b2fb8c349d1103863750b5d8cb5ace766917f4b35f3d883c8f778853eaa9f76", - "sha256:0dd655518f136febd96c05131a76a863e32fc2a1d7acd4e3c959e3ceb77d8345", - "sha256:0f06e60fa190aa7abd0914c2a766736fdc8e9f34878c4346338534b73d1b20e2", - "sha256:0f2ce963299d42fa3f22a90adc0fdf174792ffef5ff4c7ffb68260548fb05580", - "sha256:0f3bd290711c6e9486173a6ee7cd4e7f00c3971c7908c1ec1b6e5437c5e4c6f9", - "sha256:10083a8a0f4e1b26b599889e90b9802504ce5d3f7722f925bbb7ca47dd22a7c1", - "sha256:13849a1d4f54c3809ae721e9e83ab28f5ea602f33660cb84eb6ef261eac706c1", - "sha256:14c56f73e78faa1f68bbb826197cd5871994e70e841b8590829c35912ece5c64", - "sha256:15db6a102cbaf1949cf028ecf080aac76d20bcd29ad4e092574db6c6b7af78a5", - "sha256:19e31815d41cefc365489e591d105d2baceb2f65aa75d29471fbdbda8651e006", - "sha256:1a53de2772cfb74559df2eb4456ec4eeb908435ec55a84b69370d9d745d62aa8", - "sha256:1a8adfcaf96f587ab138476eaddef95f29b8a2a8a9afbfea8d2fd62180995d02", - "sha256:1a938761c77e0e6edb0c93d02f4e988d44a69e5195e5a3b893e5553311347132", - "sha256:1c9f3c25df6c9d3bbae4f6fd3f514b1c5c740a2110f56ccf36069c85417e289c", - "sha256:1ca5ebe6d454f1e5496cf052386a559f1080bf2de75bf327ebca0a6003b79f19", - "sha256:1d804e4caf5d5da37d6dac1325da5629ebef1e27a294c2b568b295814aa36c7b", - "sha256:1fee9a16d88a1c4865610de31ef5c666671020d7a81d1f510eaf3c97d00ebeba", - "sha256:23136f5a564654eb61061ec6d5620a4c1ea32c8f552b65e9982a12b72bff601b", - "sha256:2784090c30a586d5b45197bd9c32f87fb927216cde302f6cfd76d76577e90f08", - "sha256:29138fef49828542e859828107e42e50d0e587c513b7eb4b2d92bade2b0860fe", - "sha256:2c1aeb92eea59d824f004341b26d5e4b47a8a441cf9726769b9a90abf9d0e08f", - "sha256:2c5d675da8f1cb5650271c8ad5e95c0a3e5a183c105e72d953b12877b1c8d0fd", - "sha256:3100d169ceb7bc8f05f89a6db11d1b21f119975fc26f29dc45a472e3569f0879", - "sha256:31199204b3ced121ff5407a2c342326a5d27e3870abbf94bd80dbd2451b7bc8f", - "sha256:32217133dddc58c927805cf6c0731d8144584176b768042ee886d51e71860bc9", - "sha256:33fa55b990f81c2927e01399ace0d18926c69d69baa8cdaa819424132fb97987", - "sha256:35ba0263bae5dd3ad5aad767cc9afc01a8598c7dae30f1b3b2de98b1b32c28bd", - "sha256:35fc236507fb1b3138f0af5ecd5f94ed752d4d6d826248eae425f86204013eea", - "sha256:361f7206cf341ba94fb015688f5c8b480f8e63bd58a4c14a48aeca7851a241cc", - "sha256:369b5aa01b241cd3fea6890bdbb11a1425d87bf1515831500d518f4223e9d72c", - "sha256:37245ca4105386194dd1d292a6f2aae09bfe1bd7ac6f9ec25093e3cf8e9b143b", - "sha256:372c063f37480f62c1ae32dc3a1a0a5942b180c883f99789075a8a8ec3c4e709", - "sha256:37a9ebe00c698279213d56e6c64e1962ab1e092918270649b397cac3dc196ca4", - "sha256:38c9986f9ce50c459b10de216a05f4bd7ed5ac63887d56e500a52bb464b861ce", - "sha256:3adf06c66041aa21eeb8a71e82379b74773298c8e6d3d839b151aae441a99b94", - "sha256:3c95601ed98fad3f6e2f8fe809c3b526b0fab31ef525e00a155e227f3d17f58a", - "sha256:40aec5299e1ed71fbb988389059da381c3c1a60e0c649acceb2a35d9b128848e", - "sha256:43124fe172ada86d03ac3c8dc8179091341f6724d5e5d5b160e1587e4cd3761b", - "sha256:4736d350371337825cac1793c9f7c40701c32a03912548c2a7608e51679cbb96", - "sha256:475d04d5192eba487a3e2f935976340baa24529046e9c1c9c7a3b7bf80445ae1", - "sha256:4a409ccc42aefec904038695d5d7fd6d8f3af2721b7b6401d55135ec7d6d298e", - "sha256:4d718fa1b5f0d0dd75e86fbbc5b0c93ea3a5d65216c85c61cd5d7cdddfe08455", - "sha256:501ed8b02a5990c67a91c732843609d43a6be1f7576fcdfc867331239f37fbd3", - "sha256:539c2cd5fed0947c135cd7eabaaac55f48300dfa1de0f3ca4edb5efa6606f471", - "sha256:557a4e1708df428ebe6c3081c83a273d275dad2446ce0d81fc648ac71afdb18d", - "sha256:56d834b74c993a7d7cb2b8ab33a0d55c3e0d4a3d2f2da2808a4ad3d79189711b", - "sha256:5800368526647146978389dfaa46da3356291e9f0fff9a4ef12e8c2bef964a0d", - "sha256:59c123d0e948d760a5f930f316cfefa07e8d632ab84327c0693ee6a88171154f", - "sha256:5b30ddf7234e611ca877575b62840e6af5977f92f1f9d532eedbb05a44ff8004", - "sha256:5ecace251ccfa705bf3d7d35c5032cf750f5c629809740405697bce5c118c4a4", - "sha256:5f89dad732280e7a10b74d40b91364f88e13c3f2c08c2ef83a8cd42f7a61af2e", - "sha256:637f4ae36264bd7b8d9a60193acddc1d735ad52e8ed53a19931ea6d921fea8e5", - "sha256:63ada7ee2e9345695f9e9bc4c65d72222253f07b1ac94fd0e37555cc6f3c7f60", - "sha256:6441cc837aea58be7d9baef1b2383eb8311ab9303f500f99ac90b584cd78bb14", - "sha256:658f90f49cf5af2441cad0a2b801c3ef520471989a1ec55bcb25b255b2ca8d2f", - "sha256:66987aa68b0f7c2a1cc5f388ca962b8ed92b10f79de38d6d0d8c716154f519d9", - "sha256:696477ad71385c4795e3b8e4cf10b0d2c28c2a1ca6a955e031cb1e62993e9ee3", - "sha256:6b7e54fd883671d1a8810704851c044b7173287c552b9f5c3d9e0eb9f00ae194", - "sha256:6bc94fe17c3c56e5418f79515b786b101845f70609b0d19d0c1ba13448e5633a", - "sha256:6e6b7e3a1520c39a2772f414cd9ddf5995f77e4e823dfa1522af383addd465a3", - "sha256:6ed30be8918e18c8bed0a2e8b70639ecf02feb61ed00ca2e41cfcb2a50fa3f42", - "sha256:73533644e1f69ea1164d56cc6505f564c6c44072b646b66d67df2d31fed0348c", - "sha256:73bcafa21a78d0776b3ee7cd2a63c66f968eb7db8e8d594e32d7329950f6e828", - "sha256:7814bbee202acd3bd240204c17d8b87a4c48c81064fd8674dbe527d94d5a4290", - "sha256:783ba7d845d79ce976afd9c1e91a4e5714671defa198ee789e8b23316083a485", - "sha256:79da2491348b30810728050b4a8ec0416f85884125c2fd44655b3d01150d9c3e", - "sha256:7a0c98f6a636adf0d7edd60c61589eaf52d149239af754d0bfeb0effedba53a8", - "sha256:7ab379f95caee071a37cbd8be86d4c65accc651d391f9f97748fef006f38769c", - "sha256:7bf6478188f4e47bf5686e8a33da4ae28bf43b1b2528d9ee144d28492bfac60b", - "sha256:7c6eecfab7ce4cd9487ff8ba936fe38cdfd68c04faf3d5c710363c6a8e695659", - "sha256:7c708566da8014b120a64b1eb6d200c6c0c8cb36296383723cdb6fc82038270b", - "sha256:7c8d5882ba25ac0282258be435d8a05aa0cbcacfce15799154a338f847f159b9", - "sha256:7fc59e9ba821b220944ccfe0f89c9dc4745f6d097569992356eb03869f21e953", - "sha256:7fd79c521f6290c69125fa2b85fa65d9e657e6a8ffaf722dc881b926bef4aa5c", - "sha256:803f8b575a71b1b299d677c28db0653459c79b5308874efec813f17b7457c7f1", - "sha256:8050e75af7e4c6e2d5260b84eeedb618f3e452e66473432e085b5d1b81429299", - "sha256:81cdc537e0a42e3c0170752fcadbe450246d5c3b4b7231d6eb9672456605ac94", - "sha256:827c92145b3b976b39430129c89d213b250dacbe5fce678e9a03940e6e848983", - "sha256:854fd2f1bc6e8a56b89910b5cd7261a8b40f13ebb31572da985ce59c7da0886d", - "sha256:877ca17fdcfdf5c397493a71e5ff97a87bb181417fe717fdadc77c08c09301ac", - "sha256:87cc632c88ee5dc80e12681047839304d98ee5c9a708d686505767001c9b8b9a", - "sha256:8879510a76940670517ea1cb589978da44b86e286ec3e50d664ef330817afce7", - "sha256:8885e3808aedbd6725b921fb67dacaae0678933561ddd47c2b01315198c70e2e", - "sha256:8b193bf7a443c97d81c47052f60c486071a4bdef4a573fa2514f920089414d45", - "sha256:8ed78ccad4c7b421804f5d524b7740946a7ef75d89dc0dac52e9d7c24c472410", - "sha256:8f2973bbd2bebd9d2e0cd6394c1292a1a19ccd56bdcbe1e174059f1a39be5b40", - "sha256:910d4260512660484c0dc1588a316fbb35a40c081c36fc51d1225351af17cfe4", - "sha256:95052e8777a86bae87c0bd0b5ab22d809e3d1d02bf69e3e66ddda5ba75a05805", - "sha256:95f273bea318a194f656527ee2ed19494327bc500b8e87d9358e3222579aa28d", - "sha256:963a8d8f97057082679523d0fd4c53a38f86bc58cabe4556faef682ae53fa2fa", - "sha256:976fd7689d69ec78d67d31d38d396d8adb562f7e8368279f76aed4aa451fa06d", - "sha256:989261c5f1735a165f2e4e87cf6d5f17ab734fa18f9ad0383d5adfcdaefce701", - "sha256:9ae9614c317c50836689ce2dfde07c05fe0b16378562e2221746c3913ede3c80", - "sha256:9b0124b9c17e9890f0819b2e7a5f65ec9a2f5aabd6c8e7ad090c1675cf70dee6", - "sha256:9c4880d017555d70dea367dd49271830842d48e3891c2da97da7ce8c4abcee40", - "sha256:9c62e71e6289d78c0108d8aeb495f8bd3cad4bc1632fedddc9297ddf287ecc20", - "sha256:9d0a21cf76153de8f2d96a877991d6bc59b9ab5180b949e50db73cc193b4a694", - "sha256:9e14d17773b1b3c758ff153659a1824608a0cb562c45f484b5ed8a433428444a", - "sha256:a16a1dc8529f9e734a41c3b856f3eae7ebacdc061dde3f8a844e0c7889c97203", - "sha256:a2212a0c842c723d919ea4a22a9296cb6b244b386e4e6ea92adfc7fbf3095519", - "sha256:a313bad717dde740959d50850c315b75fd4eb0c5e6b4dc8535db0f1c369be125", - "sha256:a32b78c1e52ebd8e247bb68300b90b233300d8816faa008ed0713bc539fb6af0", - "sha256:a3ffe881246d28a862f1985824f484cb7361f44d6b99c4c620436ef56462f38d", - "sha256:a49ff5cdb33654cb7d6a3c377aa2a83ddefaa1db31eb10bcf3c180aa84f9af8a", - "sha256:a8b75dd3d3638d9a19f23e84af4ffab3b8940422c0df2da2a77005ef5aa3d7ea", - "sha256:ab64ace1a68682d191d9bedd9d4c939406ad86b1d9f628180410644249ad46c2", - "sha256:abc7c2e4b47bfe6a9aea434d3fdebb9597ee636e914e92ba352f2068b9142f4c", - "sha256:ac348379cf4de5538a0a213be1532d289aa801ec5d267c5909446b9ea2f8e2c3", - "sha256:ac51cd64bae51c462ea58ad2492c9b8209667a4ef60c45c4a304518b67598d5d", - "sha256:acddcac38adc8342ba48aba98896faa7928854bebb62542362138655b5367ee3", - "sha256:ad4528cdce058b684f75fad1faf4a6a6c992fe2f08376370ca66e4ce5916a84a", - "sha256:aec65b53a07f580606593f877eefbb29a45939bfc0d3fe6e6d9f42b41b749f68", - "sha256:aef74e9beabbd6c4aafc091dabff86d046ccf013ce1e4396c0fbb01b4cad9de8", - "sha256:af1b5a92315048c3e36bbebfa7d4760a9c3e910bc4166f11b20d77d20d6bcfca", - "sha256:b22ff30006a2f28f8bff878fb93413cbe3a4d1fd517c28081d848c90e9cfd2c8", - "sha256:b2483da477932ad1983d1d33c18bc3771c6fb00cfbaaed70a875fd547ef8e840", - "sha256:b2f0adc22a4eb31e545221d93fc73a0f6a8cc2379d0f4309f71d1d17ba938b82", - "sha256:b4c9e5d05b126b267ac048a89a0e2d9b48b1b648add62d4872906304a8590610", - "sha256:b4da208a63434d21a3df64d29758e650fc4aa8cb05848554b76949c296539cca", - "sha256:b5f8771aaaed7f80e84a4e471d2f29ab6721e4595075e54d03ae1ed951b2000a", - "sha256:b65121091567847a8cb520d364ab22ba90e00d3cc55fa9eb34bb439f0684bcd1", - "sha256:b66ccc5c2cdd26e74fa5d4c29ffae424cc6148bf93ce574821783fb3b6d452c5", - "sha256:b69651732c64afb691e50cdc3387cae305e0eeff8804fe3e3ce203876494932a", - "sha256:b828cf64d62dc09ac183f03c1aeedd164ade96a2ce4934109452edf29de1dd13", - "sha256:b8a65621b98984a62e59403009591b8a5a7736273aefe1cab64cfb85b365cc07", - "sha256:bb0d664505f4b112f384cffeee82e91e3f6448d8e574989479db4439b68cba05", - "sha256:bb58ba73a3f96f9a3e46b1fab69929d7edbbddb3133ee74b5c3c54074a53c4f1", - "sha256:bc94a68ea5e18f8e85dc6b522bcb53093f692c8eb62b4837ac047da73956cbe4", - "sha256:bd82c4977196681a499bb6ca9e462afbc5c91c1c15b6a991dfbd72733fea4dd2", - "sha256:c0c88085affd35c33e124e36930c5ad96aff9294ce195eaa0fd9cec962b64a82", - "sha256:c148e8b596000dd3e4bfe206e70f3e666be18d72032e0012555f2373c52e35d6", - "sha256:c57541034d12b215ab0a2bfa371d1a8a198da18176d0426c27105b9161a6862d", - "sha256:c9648ed33dc8179e4ec04bbc73bd7f0038e1e81217a69467f61f02a78bf07e88", - "sha256:ca65cced0d67a9039e93bcd98a369920e499bf98296844ff56ead01c9085321f", - "sha256:cf22b43f35b7dbb9f71e8ee2041b5c00029cdfc28ede3a2f31cf8906f9a6c126", - "sha256:d35a4f1c63f07fbb8c8f9946dea98b21eddf6c57421585f71d91864be3ba2a24", - "sha256:d3b6e6840421c83ccb60398e333b44f910b0907bb409597685ab2eedd1e22eab", - "sha256:d73fed4e37158ff00cd271871170b79e40138db51e625fd352fa17c6acb34f67", - "sha256:d7bf9e43282d69561618e8a0ea33368d532ebef42f15c096f427090521dd74f3", - "sha256:d9d6544790ba50438a9c1a903c3c4afb1ec8a7832db5518549275b40ef0dd4b1", - "sha256:db4d697b18b6ef5528b1f36bfa25072cd2a421869f5963bc0e92c8a34b9e2800", - "sha256:dd9a137a4a9becda3094f3831cd026380f75f6855e051eefe4c73ade524f1cc3", - "sha256:de7738b8c0bb74c4cc16bbd7fb49fc2bcf6430dba11b3432cd52768ae40933e8", - "sha256:e3b1aa25f01238886a6baed9e13b9a9240ed344346c79ae280ae65e8603b21d5", - "sha256:e58952f04772f59f11c6e007471449809a30165188669bca8fdb19dde40a8f24", - "sha256:e5ccad4b7bac125722f48d6f862bed3b514d8526deea06316bb72f152cd30a7c", - "sha256:e7386aa18d98d6b8af44b92173654ec469237fda35f8e8523e43b581a86f476a", - "sha256:e81ae656b9935ac4528a71f96bb7a14d949778ed1897c573d3e7ebb9187f8841", - "sha256:e8f1e362c9352b50ed120f001046fdbb80810c9d56580f4c3fc13bbe30823387", - "sha256:e96ca64383efa107262ee3949f047af5ee4f1845ba09463466c04d35a83bd3ec", - "sha256:ea999ae6e80e66ad5eea287860951b033d0104ca34d6d87c7b5125ebe0e12721", - "sha256:ecc89dbd4155b2f8a47f4bbd89242a35ed15e8e0ec581cab5ac65fa38407329d", - "sha256:ef01d29fca550ab871fd99154f82c6472aacf8e7def272dfb07b460123850390", - "sha256:f04551dce5a7db8c9659f2e4245494c182d0663b83661803e08d46bfcae5eda1", - "sha256:f0700527dd5bfa8b7204b08330542f4f388899d3c14d885d8a368992e0eb562d", - "sha256:f2524ec55b3e65cbe235a8b3c36e2af3b635be02ed05e20c94e70c5c943c009e", - "sha256:f5844e7befc707367807586f550fa97e23dcfef0728f02b98c7bc498a961a5df", - "sha256:f9dad513626a33670f17cddc6078e30e311f444c957e8dbfc5b2b4603c8b4edb", - "sha256:fc0dcb22fa9aeabfe3fa4e0430099acff985ec5d77a851382f76cc6146e780e5", - "sha256:fd882aa29bf402b62bf1fd7c19fd5df4b6528cf468a908864b39368572b662a9" + "sha256:0179698c3c913eb64f32397083747fad20ed0f0a2b7469a08cd1a8a95d14d90e", + "sha256:034b0dc1b7fb8279599c5d8563f86abb4d2454735b06544ecab23c54572ad2bd", + "sha256:05d12b4bac53abe0c65f3163af2b45894e2e1c0cc55493ac784d52a350047d88", + "sha256:0604ff025497a050a2b2dcc4ae0e5cb6477c525e57b89825152c707e88d74d28", + "sha256:0631eb5f49f67de10bbdc3f64141326dbc62e8d319900966648381ce0845d8ca", + "sha256:0747a83e7ae617793181a4763ee8b84863cec5c0bbbde70c4394e4c0276c36de", + "sha256:08834fb8b20e1a985c70e8380a10940234b4162de62694458727330376e58b33", + "sha256:0aa1ba3ff7cdda05a1242490612976b2ae1c90fc6200903ef8f53815dcb35c5d", + "sha256:0ae91de396d5c4ac97cb24dbada3d5c91a51454781e0a70476b008f4e879e4f0", + "sha256:0e79ed92b1dece6bb57e9b46effd74d7a5d3d00187c85466d880ed184239a698", + "sha256:0ead852a5e906a43fcb6784eeac480f6a67919a51d480c1f80d32ddf9d615475", + "sha256:10202ba98cfb3f7eb60da7ca87a2c458a69b7d0d6e4d4388cd6773ebbce89085", + "sha256:1101aea5c3eb1d26e090b931c693488af0db9f3d52e68be8d4cdd807dad9841d", + "sha256:128ea4142f81a79d430f3d0eb55206093e5eda03a12abbc7b03c34748ff6116b", + "sha256:1348ddc076251cd542f4a99ccda4b7c1f8444e8ab489d3541a978ca5901c7c1f", + "sha256:1401caec21fd7f002e79ab6806bbfd1f54bb3de6d5e12bd91c6685dce16ad2be", + "sha256:14b1ce8579a43dfc0e592d93fb1d63dea693e4977980ac4166f26d494cc7a358", + "sha256:159976f9c40f96e3fe0952b708846a43a76bacb114e9cc828816f5080bddd5ec", + "sha256:160bdb3520fdadcaa21e1b98aab2e011265070814ecab3804eb61674becbd400", + "sha256:16b21164797bde6f417066d02775975cc2e15ab8abf80efa55fe85e0b4894020", + "sha256:170ba61761f59ab92afcc86ce5534a3f3d0b07c38b339b950a83213f22dd86ec", + "sha256:18a447d46a3a2f1e61b365cbf5627db7030fdb707dad70c4f2760e5144166ecc", + "sha256:1df055e51fe7491120cc84f3362bd43db186be78d0e4c476acad45e435af9ffb", + "sha256:1fed3d721f75c25a9fcdd0e362af53f4b20acbcdc63081112f85419ba0ce3444", + "sha256:2128f3358335e0c83688ecb40c19d9d6606cd60784dfbf2e24e980ac2ba87b0d", + "sha256:23f6d325241b0db006ca2841309ed17622137e134930a740a8f1331ec4404791", + "sha256:248dabb89b5aa90b2f7e43e045f048f7e5392ec77b6446d80853ba7117d7bbdf", + "sha256:24ad4921135a1410d95b1f1504f4901e1c64cea680014ce2c3c7a825f4f259fc", + "sha256:274023bf952f849e0d05eba28a4c1f65f9796430d2b09ec16539386c0f76554c", + "sha256:2a964dfeb2aba3663f0536c809aa1ff385f065e89fae57e883fb7edfb4067c2f", + "sha256:2ba6611fc93c4b169d0e0ea376ebf4b8a529933d1f5f2c2ec7d8f8b93ef58ec2", + "sha256:33376418ab2846b931a72b36cfa16810befc4f49485d0b3f4dc054a4d6d00038", + "sha256:33389fe084e5426d9fd85d7d9ca91a29cd0d88a83c7c96e411aca49a3f9967bc", + "sha256:34a35be8fb82d37087e8176aba907b9459f03d0e293c80f574c6337a436f4eaa", + "sha256:34d2ee98e15d5cfe782a431bc913fce3b58cf3fdb34fcb437aeb275cdf9007ab", + "sha256:35534b366410a36bb3d6f788691e37a76e4d1da48326b0ada3e5032580dd76af", + "sha256:36b14886aa3e0b8786ecdaa196374422c7b1c1dcc8764d02b2409f74d47914bc", + "sha256:379f477b98a1e9a77ddc3ccaa8c709d3fb4a288ff54b96e171e637b55b4adbae", + "sha256:396ba9917fe489ec3a5942ae3e29e91324c8b9956f371f7e124c971c71379e7a", + "sha256:3dbaa7f7c2f0ca8578895fc61fb8c8e50ebb405dad8982f92f4343285c7a3fda", + "sha256:40f586bc8a084a3671ddcae9e5fbd3228a596bfb63d9f0380f153f9a65b69f08", + "sha256:41e0c3350d08994ee8640c39884e16514e282f70ba40f5b2299582509a327774", + "sha256:41ff3202cc23c800507777df5a4805b402f262b31008c60fdc652aeb6db2f278", + "sha256:439a19f7fbbff232ce96682c57e27030b8ac3a4b8121484c94f04bf99d08bfff", + "sha256:44f7e5dd83a615636b80182bdf446ece57ed61d5d51854acc5d9840631136d4e", + "sha256:46d4af0afc6eb9867b3ae50605787c80b868e2f52eac3801246034925fe578b8", + "sha256:4b5c41e44da74383c924cc5d75ef0a268f301d69305b3c42bd17af685d55e412", + "sha256:4b87ad54e8d4adeb0a1f04889504d6ec7f04fb02609220810f51f1b6c66bc1cc", + "sha256:4cba2b0b9235fe10e12301d6b4cfba0f353fa668d635f6e988b03623c2cd42ba", + "sha256:4e11e7299079718c78f8147e7206c22fe35bab4466d38992420795288a0b8096", + "sha256:507151e1e3dee95e9e8159e329aed4f75aa5205ecd6505a4f6be546890eafbe1", + "sha256:50acd7ee7096949b04482cd7720cb6b85eb9cd9dd5d7ffb6704bfda250261a22", + "sha256:50fdfcb03be719d9573597b095b1175d2e9d0b30d065791dfd9fca727c499442", + "sha256:5129cc1f5fec6888e2db0be936dab67242e32c738811c8769aeea93aab4257a8", + "sha256:51d7f33be9a4a1a2801430846d72841deea0894eae8381a07e7d90e0f71b3c4b", + "sha256:535173fbcc3933d84f9929d49d7a59a0faec259ee07d07c34c7d2a980b4e3683", + "sha256:53daa47dd176db64bb35170e3d5d0ae2388c060121201883696278f055a0e70c", + "sha256:542429c796430de924d03b68a6173bb6d79d5c4967d4e9a18de3e501cad55593", + "sha256:544f2642a456fa264614e975d921540ee8c3b368b04d5aa1ddbec33241b13e08", + "sha256:55392202cb374dd1a1f89a8ce1586644870d9e936752059d053e576acc50bc89", + "sha256:5c6455f2c11daeee40665c67494cedb426f67dba7375710524071c0c56d739a6", + "sha256:5c8074ad4d67067c87bd0663dfda654f786336078c8fd7d2f6c1aa41de8494cc", + "sha256:5c93473d0d7cd9bbb370973a9679a62f381c7050d7dff4ad6aaa92e8650f5a79", + "sha256:5cc58ebb731200ddb64d55f1b345630fb5f7a8138cdbd242af9dce964a7cb03d", + "sha256:5d19bb1ec12e385c09215d5d53a243c060c7e8a0aacdba16d933e22902ee380d", + "sha256:5f21fda91bd6c34455bd5c312e42aa1334da46cdafb4c533ecd01e0f7f19250b", + "sha256:5fa1484f74d011addf2e5f5a0378ec41521989839a05d6051d8067d8ce732423", + "sha256:5fa296f14068538fced53c6eec86520a2ef3d3d27a0fb134640d03e067986d5f", + "sha256:6120aab922bb3e15800b6655558cf8e0a5cc79518e954d457f064e5b3d5e9bf6", + "sha256:61a4e5d81b8d4e4ad61964b230129e7a2b914793d96289029078fc9009f074ec", + "sha256:67fcf28db77b385820881521db7435e9f1c607cfaf07db6eb78aa9d1146bde86", + "sha256:6ab323f0c5490abaf35a78563e1043c7a772eb86d93f359ecc0fd286d1cd3807", + "sha256:6ad60de1f4c702448fc8f1449f05e810f6b7957c08a5b3950c8a792dfb13b50a", + "sha256:6b7cd1cb0b363cd43ebf499beca26d201dd8b89eee49fae60205c82ba13ee03a", + "sha256:6c2144785e42527404bbd5cfd11981fee4abe59a22aded0e498eb711a831d3f3", + "sha256:6c9fd50f636a8fa9cb6324cd3eac962fec2bc5bb432452a3b583583a1059acfc", + "sha256:6e7f70d912a589e30290ed926f90ddbc3160998359cbad7c9ede1bcee481748c", + "sha256:71196ebb8d523148e5975396a444de02367f204b53b14e26794c96b2be0ed742", + "sha256:71acdc6eded0f4b86b5e16c96314887cf2572a8eb5d8038b78583d0c0eb3aa1c", + "sha256:77024596b9046572c4e90b34c1ff212346756dc48933f90c53cf6e233660788d", + "sha256:7a90453a79423cd7145cc08fc92322dcd7aca4862258f533e03f473226d4b835", + "sha256:7b25c335fc53acf29d4d21dbc19fe39d2824201cdda0448623152cc5917bd259", + "sha256:7d0b4fec6a8d02d7e95de5cfa913261820f1ce04bd4c0381924de0da523179b8", + "sha256:7de54b49e6da811b0321e412d14efdaa1ee0c0b6609296ea5b9022bc5b2bd843", + "sha256:7e0bfa161df365ba3c88899ee3b7c94755200967284bdedef8c1b8b43e2c0f2b", + "sha256:7fac4250b37d994e3fe42b46ba3c8bfa1614d1d7d8cf1cf23f303099082a9565", + "sha256:7ff8dd079e7b5f3438332499233a2a5acfca0741fd0eb3d4ddba0c2d9bc04d19", + "sha256:8090c35199d6b7bc6426bb8bdaf341e64f295cc2624a1fda7860c0837f1acc03", + "sha256:81a0e08c64dfdad27dab687b96f572b23bafa1999a39d1b6f70b3ddbb73e8bd0", + "sha256:852c921217f330b3e81a822647ebadeae7e42cf503ec1992d0bfbc90121c09fb", + "sha256:85cb3ced4fa84949cee12bfe78208b6ece7baf3cbd242b26dcaf773efff8d206", + "sha256:86bc779a0896e59e4be30a5be5cd6eeffd0b40b6f0e75e730218736b7bfc6f5c", + "sha256:88811f890db240a1c82bf0bcd52973763707a552c8113ac3fcebca183afb2fa8", + "sha256:88ec4d16e9f58071c9896ea01c4da97cce9d01418fe844ff06eebb00e0a1386a", + "sha256:8a844b8b1685f38a2e8b2f3213b286e2a7abfe67508381780a0d4599ac337c1c", + "sha256:8b8429361241da973e594d15344a0989f44fd288ea58d33a6221fb7cc0daf27e", + "sha256:9161eb81b8062da824426d3700d4b0d287f0cb0b05923713adfe3bd25e7937ac", + "sha256:9267bf8261a779abb2a6eab5f107f5db85b2d1745f2494081c731aaf28738ce3", + "sha256:939d8cd2d8c35e3956f6bc858390b6ccb611e6152b4920d64ab5e98f3fcf39e4", + "sha256:943a9bce22180ad0f4d32d1b402a0949a4ecfe5a1257b47f54a1b51981d81b86", + "sha256:966ae0588ac9959a040220063733b33f321d04eaf4e60349b42cd855d232202f", + "sha256:98beff85392ce435b28a0971ec21cade61ce8be8b632c9d855475a28ef92d31a", + "sha256:99cf27791129d37e191ff013bfc29bf6631c29edb21680c00978567b91fc5d6b", + "sha256:9a8c826caeb7c08264e0a556df1267531c6ed90cc70506e7e5f4119e2d09f3d7", + "sha256:9b24e1f93b9b586ec03bc7bea1bf021ec90bf2528c729195028a3ca1c266b3f9", + "sha256:9bc5e7f843d14a167cdc26fe2d22f6f3aa2feb57919cf3ff034262a57d8d95d0", + "sha256:9c10791e9f5ef132effc8fdce2009482c1cfb26618c5fc1b7952a47dd5eb632e", + "sha256:a177a0ee5cf19931dcaeb3f662bc562754cfa4f4ace2351d9da24a954ef7db94", + "sha256:a2e575129c048bc286d696ed8e49ca148591768b2d77debcc6569f6fb64d0668", + "sha256:a5f0bebb10aae010d3c9ee3abaf83ab2069c718457aea09c15532355dd7e061f", + "sha256:a5f721a2437390ab69c10c6df5c142478d399af8dfb02e6d823cf2358e8a4748", + "sha256:a60b720c329c0007feae692b7bf91cf17b3f9bd3727be96cc6f9a3336651041b", + "sha256:a8bba9d1f6db4ef2a6ebfc937a65d36e80e3aada00b382eaf56fea8f639322d5", + "sha256:abeec7a89d698aa1c9b4c36bd5e3c746faef0867076e6a2ca27fa5077c4ece26", + "sha256:aeba2c750102051aa51e087c2ccbc79f2724a41c94168f8731e36f54c453551a", + "sha256:afe36ca503c2ffe30fb6df82b20389fa3c4035b5d65888a61310921cf3ae91c5", + "sha256:b0e0040b0d8dd89bd0af9ab18901981e344ffba68bb30b8eabb4eab6c303279b", + "sha256:b117ed1cd1a23df0902461c38093408b95971833dcee629112acda25b603c8d0", + "sha256:b4674b12701c3fcbdf7f88b9e4479701c93bec5da9eb576140d5fcc0092990af", + "sha256:b4908e17867930b7ac77f89a18dc67308c67c511f037d8580489be86fb585912", + "sha256:b57d4d7021bfd159db9f8f6f862a85a7a6027934643c512f028d6e5c60c4cbd2", + "sha256:b5ed78742502b8d90ff2816688d407a097c8b5cc6af4343fc5ad7a98df53a7cd", + "sha256:b78de22bae456a976f33df34d598dfd16edc9a03df8f4cc8b7c17bdba4c97b4a", + "sha256:b7cc5333fcbfb27327d12612ed72322f221b61c2b69deb1155078c964f86e1a1", + "sha256:b9d9b7d72975521434368fe8aed3f6b522060bf271adabaa5ca6c87c0c08e168", + "sha256:bbcae7a54050b7ad7bc7bf425ba63dea7d2cd31a92246ba787a2ce69a9b98dbc", + "sha256:bf14cfcc30b097583d698a6e2b8b68c9bcffab277c485d481881958360c2938d", + "sha256:c39dfcaa0bf23443474c0cb58d8d8aea9529c1841d99654cb38e4dada7b1948a", + "sha256:c44ca6d3cdf4cfcbcd4f928fdcbe87af5fd7319f6ad4169617b7fd6b4527c33c", + "sha256:c44ced5e5168cdf677f0ae39900863bf2bda7d14a5e13502014005cfe040b8b4", + "sha256:c45629c0049fbdef932dbe408ac2b271fdc8c7d9962ca31160f4a0fc3455fe4f", + "sha256:c53be0dd676484a660acc56e4f1cd0dd74bc1255d12fa285e86a3fa9d5f22bf9", + "sha256:c54ae1b89e582aa25f213cd8b5eac0bda1724e79299f486baeb3f562bbf82ca5", + "sha256:c564d0758748f38aec56a6b98c6801a427b3a63f39b7cac538b2b2d18ca32740", + "sha256:c5e4a362a95b85301d262ef6bed06cc8e4a144ac7e2be874cb4c3c46ae89d754", + "sha256:c6b67f08014bfc4aedc22cf6a21010c2530cd5fbeb655730406827fe196296be", + "sha256:c7aafa4dd2f702ee2198005d6cba4309c1e25ed1c201d77beddefa47411bead8", + "sha256:c81062e947f4b5a624135a843f6ac4b3c7fe6508300c9fb27347f022ba0c513d", + "sha256:cbec738d2ad551c6f70955d7eec95e339380ee1564e2afe86bfee05fed52ceec", + "sha256:ccf98ee859fe29f874ddd8e637f14ba59108a333492b521acb885a9095244a9c", + "sha256:ccfb950359a80de0fcd2030ad60ac1b1a861462de3e2ef746697c9256659af21", + "sha256:cf606cfe3f67984b4064ac605d71e1eba12515fbabf5bd5a34a8952b8800dc66", + "sha256:d02cd23b5af182a49d635ee72be38053767711987a9fd82625b16b93828a0d8c", + "sha256:d1b1b32f3c32f734dde8f36ac1df8e275e768a7b333241cd637cb2538628a4b4", + "sha256:d7dd46a8fcd7653c09ebe67eae9d4cb6636c7a905d9cbaf587dabcbd4eca6013", + "sha256:d9ef29cfd98e17085b4f91bba8fa1570bec6787d5c52ce653ed33a58785585d0", + "sha256:db77888081431aaa69f3fd3480891746ddce6c2a571f6201869a24e2f06cf423", + "sha256:dd8a6b3e8f9edb07fe671b02d8c3241c8b641fecce7eb1e36432db3e55e243da", + "sha256:df03e392cae1e05462918abbae06d6100f1e53f67db971ff0ac6c07d9edf7321", + "sha256:e0d91a4bcb59ac0d7af0d8e0da737332e1b7fe6831e53e47819b1b5349d431b2", + "sha256:e2e718fa9d1d900decbc240a533d5d0baf0947ef464c78a8cd4fa32b4e8f590c", + "sha256:e4ef15d0a29fc2da67fe8ba2301ecabd6f8733696cc2bf0a0cf96a144a20328c", + "sha256:e50f7775b66c7802f4cb697e986c5acf30ec07301efee95b396c08114e890d67", + "sha256:e6906aa4bc62cde2c8aeb8a99a7b4401b241e274ae7b11df67d863d61ab3d5de", + "sha256:e96d67914ddbf5466e4476a1cd7ff30a332cbab85ed895207acc3e58c979b6a7", + "sha256:ea027bdeca1d7e498237634ee4e3a852e2723eef39996dec0ff0f77dff8a2336", + "sha256:eb0228c809b2e7eb47921876050af0bc4214b351bad8d8112f70b6ed4288763c", + "sha256:ecc68f5e47bc6f6f889bbed5bc657b22bb2237ad9ccab8229cb5a0d64f4cb536", + "sha256:ed6b7f402f3dabd1d72c798b96cf947005ddd796a5bea7b041bccbd517859a42", + "sha256:f16ac8af2804855d3cae5fc3c5ab609c9fd0fc8ecacd92579c05ed3c173396fd", + "sha256:f376224572d1f5da1c871f969ab04765727f180e70d012d93e07bfc08442c64b", + "sha256:f76ceb623f7ff50df46ac57e1587c479d87a5766319c4f43d0c0a5158896afab", + "sha256:f79def86aee67b5ba01b2565f1610f262bf88ae53c379f93e5fa29c50fe793be", + "sha256:f8e95c95039eab6a2dad8c83c38ab87fc5431d28849e0c8a7e2a4e70ba38710d", + "sha256:f979a077d1c0a9a36dd4fab0d3a36b8de7b593bf935e13df85a380395b2c11ad", + "sha256:f996b19ac89e0dae65821ce65f788619e4286f78c62d005ecd3b75b5d9c0892b", + "sha256:fab380fcff8b3555eb2bd04304fa4330909a771a9a9b0dc07666cfc23148a711", + "sha256:fabfdd4cf97db033196b51af46b8a681d4785c2a66347f2a5af1b4bbb1182629", + "sha256:fadcc96cd6155f35e6d85845fa4fcd37b35885dc8fda77b9f851cdfa538194c1", + "sha256:fed6b7705d49dd07e5e0dd5f5c873fc44047e92d714299b13245b5fecac49d01", + "sha256:ff15531a376dc6f35984443fd1429e4b150c36ce27633e7cc52a9e5318546e20" ], "markers": "python_version >= '3.10'", - "version": "==6.9.1" + "version": "==7.1.0" }, "numpy": { "hashes": [ - "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" + "sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb", + "sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5", + "sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab", + "sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988", + "sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162", + "sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1", + "sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5", + "sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53", + "sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508", + "sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255", + "sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3", + "sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34", + "sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266", + "sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592", + "sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f", + "sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf", + "sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee", + "sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617", + "sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e", + "sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37", + "sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c", + "sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d", + "sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3", + "sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71", + "sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647", + "sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365", + "sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd", + "sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2", + "sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0", + "sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d", + "sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac", + "sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f", + "sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d", + "sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad", + "sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00", + "sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129", + "sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179", + "sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d", + "sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53", + "sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380", + "sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c", + "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", + "sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8", + "sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a", + "sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551", + "sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3", + "sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788", + "sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a", + "sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877", + "sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17", + "sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454", + "sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b", + "sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645", + "sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf", + "sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f", + "sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356", + "sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18", + "sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73", + "sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23", + "sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05", + "sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3", + "sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959", + "sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394", + "sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a", + "sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2", + "sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076" ], "markers": "python_version >= '3.12'", - "version": "==2.5.3" + "version": "==2.5.4" }, "openai": { "hashes": [ - "sha256:2575f961e4c1df8761f8eeb4d78e967de3f9b9f91b22f7120d60e393e970094d", - "sha256:43ff3de5457a8beb4afa177d4e0ba6841cfadd5219ebc62297fde6c8a9d401a6" + "sha256:1ea40a084ffe2f9f16fd3bea6e870f757853b536b18d8e737c518ef849a2920c", + "sha256:cf0509d32d7bd8eb6a3db02417957cb455212b6629d071826b9ea1f61659e2a4" ], "markers": "python_version >= '3.10'", - "version": "==3.22.1" + "version": "==3.28.0" }, "opentelemetry-api": { "hashes": [ - "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", - "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3" + "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", + "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-distro": { "hashes": [ - "sha256:17db6361693047727ebf29edb5683c0084b6571f62f5a3b44d6971e928934923", - "sha256:87f29a7d30d753e21e6914873d30be10bbcee24431d4d95923ba154fbad00389" + "sha256:bf29f42ed11397c6fbea36c1e45481d9430a27ea116719c4902bdfbdeee54605", + "sha256:c2f679de6a0540fabef6d39fb43fce4c0ceb1a506e667c1fd7153abf362c0368" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-http-transport": { "extras": [ - "urllib3" + "requests" ], "hashes": [ - "sha256:2c229b6593eaa22c86d9b8a15843dc23b406dbda00fb138339189aab07923b4e", - "sha256:c82689928a11505a2a0a26a04afe0ce06d270d3db984bc16c1a80ffa902db00e" + "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", + "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp": { "hashes": [ - "sha256:16a96045d6f5ec4ad8a3314e422887df654ab5bc8c194b631134477cc657fde1", - "sha256:3de9a5b18648dc124c751f547d8c7a93b177ddae552566981a792d18a063145c" + "sha256:d0ac35592e77663a9fabf2740b4818c57196b1b764e0c8449d0d7bb2c7b2bc67", + "sha256:ef3910d32b36ccbaf62390189759bd43a8109885e2d50b738ed9c9b255bf5cb5" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-otlp-common": { "hashes": [ - "sha256:35d24c867310f4713a9738b0202b6bade77955418fccb9e2f7eea01b76263916", - "sha256:362268ec6aa705e183776ff938539df1e8ce45bc5509d242538b1d40c26fe6a6" + "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", + "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp-proto-common": { "hashes": [ - "sha256:36495115a0c6a7aa946cfda9d59b6ed4e917b6ab0f75cdaf66bc1b176ec1be1f", - "sha256:7e1410ae6d3ed301f7a74bec96467d519d3f37e52f9d95b309ae125e5a1af863" + "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", + "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-otlp-proto-grpc": { "hashes": [ - "sha256:5a8460464a421fde245be0eb1cad6c6059a07845d1896afb74c963f847179070", - "sha256:7888c2d57b4437cdd879fe250455065ec716a3862f3eb17b850cbfe134aeb181" + "sha256:3b3dcfbfdcb4e35149fcf309972282054b45228f5c10547d0095d6578510a9a0", + "sha256:e42ecb789d2fc5d8145e3dadc3e2991c9f18cd166d7c7514e234702540274b76" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-otlp-proto-http": { "hashes": [ - "sha256:2f35496d96809f946f41b8805e6b93aec6c9b71b5fd759b75b6af4c084d992ae", - "sha256:74385f99266dafdefff41f3da4b76f6dba79d237a9d4487e6ff1ee653c74a0a3" + "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", + "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-instrumentation": { "hashes": [ - "sha256:0b32e2fcfec1e4bd48511a3b77ce32bfb1c16dcd5479396b21f9086a4b06c0c9", - "sha256:316715ed7b33a12530c0ae3e8d4cdf26d70e1aad358d7ba256e5dea2d4ceed71" + "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", + "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-asgi": { "hashes": [ - "sha256:8c31fe67b2aef6f7dc3df9c0434ebd0cacd6d8545da703a55a6f160f9bc06fc5", - "sha256:ab3f9c86aaddbf9c332534e0fce6c918cbacdab7a33951825a9ebba6b2fb7200" + "sha256:78b3f9bdf0fa38c65935a2ab46d59e0f9de873a51e0c95b0329f106e2ccb5274", + "sha256:78cdc5e45e897e16a8dac9d282e8d5bdf9af2d58e1313fa0bdd4a134c6f9dafc" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-fastapi": { "hashes": [ - "sha256:3d49e678d0957a782d0ca32ad21139cdfabe8584d82c018dc9faf6bfc5a72701", - "sha256:ad9543fc5a87b511d487aa94625a97299ccd818611cb0c979b46985ba782f1cc" + "sha256:584cf9d2c4417ff8b2d6ff2bc606bfe13c8b3456018bf94f50f2cf658492505b", + "sha256:97f8ac8fd7537517f9e6988bd0aca04bfa5aad564bcd46c245530739e2be72d1" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-httpx": { "hashes": [ - "sha256:2735d1868749ca04ce47a3802b0b809e3a00c05a65140e05bac5700e74410539", - "sha256:c89297975f54d67692d3c29e9e33a1bdac47710031cc3ed0d83a0c9765336981" + "sha256:0342a4002c6dbc6c4bf22cc7e698f50f5c8b77f63325c6f40c94ab87e016bf4d", + "sha256:5865a72c68098c85955a271ab8744b480a36e3ee492d35b8cadb93c7c4dbb618" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-logging": { "hashes": [ - "sha256:3b580f15f8b7874e58c835230571f1a46cc156399bedd77279d7e7510f76df43", - "sha256:e3516fd5dbff7a93bb159e72802bb8f96ab92645c5bd81488d240b6f23a6df59" + "sha256:2250240ecb1fce36939f64eafd988832338684564fb4c7b401c9b9af0ee11d46", + "sha256:f13761110c52eecae9d7debedb0f88b46acc248c20437e97c2a5b3a2b4f96a02" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-requests": { "hashes": [ - "sha256:053dde08fbc1d223058e33d477cf81e2bdcbe7af3893787283339b2b557d4b7a", - "sha256:2f9df08bf99f5da4bcfda70735492b4364bfcc5bd3f48232658e936f97ee1a22" + "sha256:28578f72e68e3a5be3226c618ac9570ed360ef1dd22d5d6e0721c6905a4ccc67", + "sha256:7ba17d984a2bd876b88bf0aafcca27b5e3ff4c1821e0fe07641e380a3cf3a3a2" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-sqlalchemy": { "hashes": [ - "sha256:178968856b63b85d11d96de156fe81d4fa8de395741c6f2f5629de897e610cc8", - "sha256:f7083407966f794c79c4643e97814e476c90f4fc5161f436c1b74f4d74551e94" + "sha256:a10043953fcba71911bf29a024f8cc337260c1ef0b4fc844b96cae0de0947baa", + "sha256:aa30b10d880d7e91cf94b23a92ac85cec09ffddd8f0d40256d7c510e3dd33971" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-system-metrics": { "hashes": [ - "sha256:31a395c44078dd328adf8f2acdf6f76a5510341512cacdf9f9bb523d9a98d5c2", - "sha256:40e83de3d09310b2d5754ee89ce4ffc75c6d8d3bda64a2df79176430651ae9dd" + "sha256:0654b41eb6db3c2f9ad9979487afcc6f461e5b35df282f0a11d0f465e906d967", + "sha256:b0fccbc1aecf35c182431a035ec4809b8f9607eec692243cf70f23b3bda51231" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-proto": { "hashes": [ - "sha256:96ee414f24bc3f61ea8e17dc56b4348d4049d73db3eb17c6b3edf75b5b403300", - "sha256:9731566359d7b8e1ee1e149d8e4ad6865bab965e657ad3387ec2784d16927666" + "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", + "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-sdk": { "hashes": [ - "sha256:20caa5130505e386c67c3da1c76e446c842698ced54c76c6148679539aa97972", - "sha256:5dc634c946546f61b757c5b1781f9fd1a10e96ffaf7357c797e508fe9c57e75e" + "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", + "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-semantic-conventions": { "hashes": [ - "sha256:175b19dd98c4473f4f43a2b1df59186fd7b4a48cd3f77cbe03438b6d6fda230a", - "sha256:97a77dce484c54861e7eeff7651fd8a806dd3c30e501dc316730215ec36890e6" + "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", + "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-util-http": { "hashes": [ - "sha256:7f5e581e94f023b30995f700e41ce319cec82bd542ff1c1c7c9b56d6b10bb687", - "sha256:a2d53859630abe8e5abcc4f867c5a809bb95873033573b1a67f6ef43b57a8627" + "sha256:047dea1a628031f857a5a32261dc0e955bc162d39993ed1cffb8f2cff5ba8a62", + "sha256:8f443d7abcaf29c4a07b373bbd31b5b39132c0ed3c27d015a59dc0323d5b1c58" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "orjson": { "hashes": [ - "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" + "sha256:0526a3456db67b264c6d661b5f090077f326b6cd074d0ef53a72763595dec5d7", + "sha256:08bf722f923d2100bc5e5a5dcf72c656db557049c1bea26582fdd5dd9d5395a1", + "sha256:1807c2fa49d393c7ee95fd1ef1b39cbb24aa3ccd81f30b84503ba59407666960", + "sha256:1d84820b2ec4ac975cba482214032de5b0dbdd17046170c98e642ef9c4a4ee4b", + "sha256:2715c4808d1571029ed18fd07a82140bf3ba7def0dc89f8d015c416e3649bf87", + "sha256:3ef75ed7e81dae34a3649f82df52cd85f9ac839a7d6ec78ab355b33b3b27ef7f", + "sha256:4329c19b8a25693f60a77b867c9d2a3ab637b20e36f5b7bea7f5acb492b44b15", + "sha256:45e34deb3437509f4ec9888dd9ee5dc426cfe21be10f1eb4ea3a9e4d33034f9e", + "sha256:4e5c8175e1574dcbe446ee654275d353c1d78bbd9a0dc9f209bf35c9df72d171", + "sha256:4ee06e53b998c71ce3eb93b86222912fdd9dcced685ac64d4525d36fac338ea4", + "sha256:4f66eac85b072092e9941c3111882afd7527bf926cbc717038fa3654b582002b", + "sha256:50a5202ba388b3850ba24437951727d3aa6d79a21964a30ae8dc6a059a5fd34c", + "sha256:51d11525bc3ca736fa97ce4e4c7da9999cc00bf261522bede43b4e7531bd7965", + "sha256:554948becd1110123ef9f6a6e1310fd92b2d07d2cbac6dbf65df3de75702e736", + "sha256:58a9619d88f8818d9ab6b39d70d203789457ba13c1ed5d274f33ce9ae7e81a36", + "sha256:5ef4d4157392a0439b74f7e49e5636b4ea43d9616bd0884effc0195fffcaa2d5", + "sha256:637dbca1fccffe83780e806fbc0f17427c0c59bf822528eb0acc8f0aa9f19acb", + "sha256:64e8f345048d988c8b68d3882e5d41028fca1219a9939b32e4a77be34c8ae8e3", + "sha256:65c4e0e106ccc7265b488385659117a6805c37d042f737558ecd68aa0c67ad8f", + "sha256:6adcaa85d79977659a448b4123a88eb33511a11ed2db243535ad7ea88a6668e0", + "sha256:6c8bfe728b81b0fd58a3c7f3f9c5a113f87f2992c9948e0f28707aafd737c0bc", + "sha256:6d0684895b119ad167fb4ec05113639dc7f728022deec4756a710e838ed92e7a", + "sha256:6ff2a2c67f35202f7d823753d38ad371a9b7fc297567cdfff4420e763cb9f6f8", + "sha256:7804dd1d6161da0e53b284c2aebf20f23e78eaac617300803e1467d1828d987f", + "sha256:78a12d4f8d740cc9ae197f5223682e5e960ba61b4fb2ce5a6a3bb54e83fde28e", + "sha256:7991921c5da527a963b6d4cffd0e4ea89c7e71d4be0c8be1bfe6edb223ce7d96", + "sha256:7b3bc6b81835ce65f4729ae401607583d41139c6de95bc7453f450f1391d3e7b", + "sha256:83705c12b4afde10c62a5dd3fe6fdb21b7900bd0dcd5af1c85612ae94d0ee590", + "sha256:84d87e322e1674408f85adea63f11aa19201eba082755aec20ebc217f493bbd2", + "sha256:8594956a75223f657e1e68c568c0eeb3dd145f02cd6b78a47fd9a8095dbc4eae", + "sha256:89bcf2d4bc6c9a7e1763c8cf534f38712e66b76a0fefda7fb7785462f0d635e4", + "sha256:89efecad02515df7f318d0613b5dfd6d2a1acd323a2b8294712789a715945525", + "sha256:8c2ac5c09b017c484df1b4c68b2cf250b4e8ba08204cb58e7cd6cbbc71a9c902", + "sha256:91d933e668ff0ffe164d7c2daec36beba6d1ce7fadb71538fbe142a71f8a1e6e", + "sha256:93c70a5e22bbbbdeafc7b273441e8452a196041d67fd4d9a9c450c66370a8486", + "sha256:948bad47f2e2e43527f14248364a0e5dee26dd3184691010ec4a1ebeb0fd6771", + "sha256:9825b954155b345c4759f24e5f8d652b9aec2261bb5d4e1abe06bba0a1200535", + "sha256:a0377d6962fa431c93ecd78fdea771bb62ec545b24ee0c5d4e32acf2260af259", + "sha256:a79cdc4934fe81f593072c94e13da3095e9d41c2deef8f6ff2901794ca1c5042", + "sha256:a7bfc7db961c7d96cb75889dc6a1e4ae1e91d87ee61da564f582bd742b8dfeef", + "sha256:ac81530647c3423107cf61c3481e91f57134e9ddfb6ef83f5150ccbdcbc3a3ee", + "sha256:ae1d895cf7bbfd50ef34bb63bb727b14514f259f3e3f8dd010783bd38e864c6e", + "sha256:b081f0e7b600ff24513dec4ca75507fa05e904607847e386e8310d5b7b96b6c7", + "sha256:b571236d8393edcd3236e07423f762bfcf571f852aad667a3bce9e7b755e0790", + "sha256:b74c30e56346aad067937d766846ee74c231d1d18aad3f324e9b9261de3b2d5e", + "sha256:bceadfd314bd238f584fc229a4bbaf0e573597e7a026dec5429fbf29fd66c641", + "sha256:c5e3ccaac3106e8fa6e2f2f6962449d7c757d7b067e41b395a19d6f0d6cec892", + "sha256:c749ab3ac30b5ab1ffb7677f8b92eacfdfdc5260210baa398f845bc3714c05d8", + "sha256:cbed5f4c4b88d94bcc36115f4c3bb3aa25da1563a5c3328aa3acebce2b083040", + "sha256:d1de5eb04485110c5da4c657e49168995d55e076b1ce60f1a042e254f4186c4f", + "sha256:dd61e64802d51d1e4f16531c64536354fc3bc67932dc0cff254044f72bf0f187", + "sha256:dd9d9a101bd8dbfad112170f009cd155e52bb8c936468821a0d03cbb96c0e426", + "sha256:ded33b972cffdaf4ca0ac917338ab61d2bb10d68987dbcae641c313fbfdbf499", + "sha256:e8e05549f3b30f9d8a8e28c5aba11cc2a4b90b90961ec685ca58444b0815fc09", + "sha256:e9b61676116f755126b90e740a9cff36b91562f47ec330056cc88cc3b9f02f4b", + "sha256:efa160215c4630836d3b1250af4c7a305acd8239e0d75aff986b8088c2fcacb6", + "sha256:f5c05a8fee59309f537590a1ff12d3c1009c485e96a50a9ac60dd085c09d0fc0", + "sha256:fb8644dc6d705e1269ed2842bf4dbe2b4e50d670de503bf79d5cef3a5148a4c7", + "sha256:fbbad6b9b1da43f25c1f5b20cd5a268e028a2fc95d5a8d1ade6059973bc71584" ], "markers": "python_version >= '3.10'", - "version": "==3.12.0" + "version": "==3.13.0" }, "ormsgpack": { "hashes": [ @@ -2113,10 +2211,10 @@ }, "perplexityai": { "hashes": [ - "sha256:6d6711af5148c0f804c0e6b87bb7498a6b97ad6a24fbb157de4c5af4054849a9" + "sha256:3dcf1166dc0c8ee5ef4fca22db2653d194adbebdaf3a4d9f55843ab42903862a" ], "markers": "python_version >= '3.10'", - "version": "==0.43.6" + "version": "==0.43.8" }, "pillow": { "hashes": [ @@ -2491,149 +2589,166 @@ }, "pycparser": { "hashes": [ - "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", - "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" + "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", + "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc" ], "markers": "python_version >= '3.10'", - "version": "==3.0" + "version": "==3.11" }, "pydantic": { "extras": [ "email" ], "hashes": [ - "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", - "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08" + "sha256:15fab1bea6f1dc5003b54fc2ecab230c1fd1dbade2acd4addc52d81e32416d4b", + "sha256:8a51a7aaddd60f55566d1f07bdd87b92b463903f39a8f26b71a06314cd1548ae" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.13.5" + "markers": "python_version >= '3.10'", + "version": "==2.14.0" }, "pydantic-core": { "hashes": [ - "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" + "sha256:00963fde61cf8880d9e7b5635a9830e0591edfa45168447fdc5b47635c0f6437", + "sha256:01340f4fbb4f854b1f36a6fe9dcd2b26c8936aead4e4ad1205624ac025c875fc", + "sha256:02ec3675d606a355523e1c52bf5aa4116776f8d273dba03d8336a5a3ae984e15", + "sha256:031867348c98ab49c6d3a16ad39738369ea48da59900d76d71c1e64de71fe79b", + "sha256:0393763d66f6f61715488d074a2cefac04aeb3ee281e36fb4925dd44deaf9e17", + "sha256:049b0404792dcb942f1092bfdae5f819ef30445b0d174782e1909fbdd91bb48b", + "sha256:0abe1b44d361b948404b6b2ed80be2583e0077340572e071afa6e0eda4e1de30", + "sha256:0def1dc09802a790e4a1b3cbc4401f0b53c58f273ce3671df9867f7bdf1fbe20", + "sha256:0fdeda6272d60b1f6fb63f6a3dade55e274af62e1514d24a6549a12150c385cc", + "sha256:1541c334af5d42cb9eb03862a9b4d2cfbfc670fd05172ec51f3ce02d704550f1", + "sha256:158749408ee19682b8a2a7e7135cced7f41d6f2f9de96088b1b1609858a6a158", + "sha256:1751e92d56fbb623b937d73985e500b1a2b1053e56f718a6c564660d99bb9cb0", + "sha256:1773001198030e16f946a7ff7760fc3ebd45b12150f66fd0f433780043dc13d8", + "sha256:17a5ca9c197788a6424749a09a3dce824cb2c17b72f835f8a5e330935b973609", + "sha256:1bbd7da16d8b2912cc56c9d0b85c4998a6cbf39b220f81c0d8c397c64672ae0e", + "sha256:2092ce156f92aff17e3345baba2b7c0c1701f32ba922c5de71bd6248fbe164e3", + "sha256:21b62d45f327eb0802f842c132fda6d01a8376a8077922dc4dda69011c64d34a", + "sha256:21e38a011783d8afc8b9d79928e273d06f349b52ae84edf63ec18ad07f077484", + "sha256:23e0940b7d73f405d92e98b2a05b93b16db163be13b7478bb19e1db2e486ade6", + "sha256:24302bf47319a64e5c5c7c29e971d2b7a20a190c64e59035a3df9582dec636fa", + "sha256:243088c95e23b12db9f2cd7661d584a3f00814e087489f40cde7f9feac56b694", + "sha256:2568b190075acd058513dca34f7fddc7252fc958c1591cb67a554338fc9a81b2", + "sha256:2918547195ffb20118b829fdb8938e9dc92c9527fe6cbe58572594c96362880e", + "sha256:2f28a5a299d4cefd1066a6883d22600b9ae3606f881c3015d255784634647580", + "sha256:34a4a0938eb30931baca56e55786c5a6871ac7aa891a38c8cabcdb7e49dab91b", + "sha256:350001f5573451150d919722ee095aa28bec037d6e23b86d7f04581a910fe924", + "sha256:364f62d8024997536e57865cb1c8b36effad3249bf392a29a2379ea69db28238", + "sha256:36c49d4e1769127461b609f110d91963790091ffcc2de401ac1d3b2f6a63bd54", + "sha256:36f9ed6ae1069913e4f6e86d8233e119e83e00a20c54f88faf9c81292f2fecc0", + "sha256:38b03c5439c6a7a952f56e3e6596dae44bc78c3fb6a69df46cabfb2f888e5f0c", + "sha256:3b2f3e44af4c6512dd73557621385a71b18094c8be81d60e5a8e73a8ce96b7f4", + "sha256:3fce74add1099da1ea09473268950270071fd56773e2968604efb3ab1d240e02", + "sha256:41b9f2821f0a105f88cd54ad03fe1392ec600618ffa8610f7c5e466ecd98c531", + "sha256:42a56b0052ac11d9d0d87b1c94a1ce52e31914fd133f269585e0f63a4ed988f2", + "sha256:42fff617cb0b08505d8123d71e6e7a8e54210d9007498f564855bd843ce984b1", + "sha256:468f7e67cfbd8231f442af6726449efe46f2be0e5a57b44fba7ccd92c6f121c4", + "sha256:4b1bae96f41806b14dd8bb1568d39b5e33b5af7fb27f7be36c78b8fa84708917", + "sha256:4f31af62efd1fd0257735b72e6716b32d4f207654adeabfe524d44baf1bb6bed", + "sha256:4f96ccd9368ecbf6685d8f6981584ab72b4f0ffd20685e747737e7e340277b8d", + "sha256:4faa766450ef44d9eabed5b65a280f63f903d1e1ed6d2e278961eb22345ec49f", + "sha256:50920d66aaab60dbc6023c3035d55fbabfa58b6d205d0b39d47d820c4a7a13a8", + "sha256:5307a8bd49158b57a0ca4e10950d31085aa35d9007e934047043ccc6d28eea97", + "sha256:55684bc850059fc85ff8dc21a3e342b722d3178993c49ebdfdee8b698a432720", + "sha256:56178c4116fc0c859786875ff4acc1c8d52678f095c312b0eff2a2dd9d6f8d5f", + "sha256:57c0e5b26d82bf31ab2b044781527b1b1a36e9ed400856b6aca5097eb1abb909", + "sha256:586699b43066ca6038f96bd71d0eff0b6c292929e3d66ff63d5dc79d0f06d589", + "sha256:588d309ce5c85448379556d72011b191c0414ee2e80d7c3f1ebe2ceb2d9027b1", + "sha256:593dd4f24abeb3ed90222f98bc6eefcac68cd5aa96fc7745f93862a802952bce", + "sha256:59f816dc04e99627a5a6352ae51dfb30e603f2cb0b7009c91dc640c533def014", + "sha256:5a07c644047f5abc268b4c39f8cb5a30e08a3c349228cb70142c7d3ed87587c0", + "sha256:5a8403eef4a66743e339102fb3cdd8c8b9016b8bd67924685893d062c88896f9", + "sha256:5d0b210c871486f5acb56d87bf00e3c5d83aab6a1ef3042629fa6ae0172fcf47", + "sha256:5dbf9f18c8af11db719e67633be0af556d7d765bee0ca9419bd706fe4b7ed9fe", + "sha256:610e9f483ac53c5cc59c2d3687224029b54eb494feec2d40a2bcfdd187635192", + "sha256:62a93a9d206a3580c975c3c1f65a869cd063844d016c004f8d786a9309b3c591", + "sha256:62ec6568896e0abf258cbcd22c406c1c8bf27d16224b21bec8f75c4ae88a8173", + "sha256:63263d64884688554fb025a3906c7b00573980cfee75f9afeb86239d577384bc", + "sha256:68421ba548f6d86c7dededd70bfe530b4faf67eb811c53c70a1be69438d3ce99", + "sha256:6e1ec4176c3b56017745937dfd4a3ec6df55f7f0e66991124499f3f79f8f8d1f", + "sha256:70df7ff903aea05383298715ea53551c8f27c8f70cbe54ba7f05606cd822e7e4", + "sha256:71061800a3c225e730f7997f8a7fad6e0d0dcbe36609cdc7e60576a099a2832b", + "sha256:718d05d4e078c7828f40ad3e310870fe4b837d93a32e7e8497a080c1f1040490", + "sha256:732efbb50977ab7cf18f01d4c255834aeb14cb2425459f7bff681a1ba3a4ffa1", + "sha256:74cbb6cbd74445ca279668790e0c10eccac0428fd79fe061fce6c9e3982ad3fe", + "sha256:753863dd4317ec8cc9eb3e6d9d01a8ef1a1726a8003b4354658d68a1ae05f9db", + "sha256:75b451ec5e64a1d4b803317f34710131742ff2e42f4bf5403f597d0af857d7d8", + "sha256:779b6c74526596a86d38248dedaecfb7851bbaf319c234be042c57acddd2c8c4", + "sha256:7987866a2569396e6765c54d643fd6a7b3b234e89ee8d45a3729a7b4b2726145", + "sha256:79e8fc9c135ef628c45cb8aa5deef8d21de13d33bb4c59a859fa73d335ceb40a", + "sha256:7eee44c3f1f8acc220a743a5ed5588949a4f18b33df4cfbef2b1277470285401", + "sha256:8447678b49412294801c9ea15ae31ea3bae7da65425268c92d40e032c38eac6f", + "sha256:84d2d38f7d163c4dec292f379e9de1960c661795442aca6c90d706436cb3749e", + "sha256:8ab9e74878172948e0426e3d27fba333fd6a1c3f9456d8e75643e8e6868ac1a1", + "sha256:8ad8dad549cd1645be591a50b4573995ee7e018f6619ddc2bc6ea44b2ad9f694", + "sha256:8b16e164205a90b1050d2f5469f8a7829db7698ad198c69e6aab6cbfb5648b87", + "sha256:8f16bc5bb12f4c581b0f40facc28dbf286db32d1bf4e7e4f4c4e0f7f4e34ecf9", + "sha256:8facaf0a16121ac82cc403a71399a061b74624c1ae2034eec9ed6169c5d16ecb", + "sha256:90874428bc6678b26434336c77931c9734ecebedf50132c179205e5a9908761d", + "sha256:92016718bcf3e6f35a6bd986880191a8da7a35aa1f5b1e97544582ef938464cf", + "sha256:9279f4be22bc3765dd9f61f5b4d38d9cb219d167bbd6f2e0ffa3368b1b71b4d1", + "sha256:933f2d639eb81a3e1f145aec415453cc00983236629933f028d9507222583a2e", + "sha256:93502b3c762149a5904316ef52f0ed1ea721e0f4c8431f964f140ed15fa61926", + "sha256:955d7878130dfc124d6a5343e1b87d2933244fe14a4a0a3e98787e8e660a8eb4", + "sha256:980c81c2ec53ea9eb2227c14b3e6de35670b2de163b638f2a803e90a3bd5bbb0", + "sha256:99e203d5c2a814facef78dcd993b0fb7a933124a3475a38979fc09cceae210b7", + "sha256:a2b88f9f9fa52e1c34938ff1a18ee7fbffe482df0bb43c087a9a60578d273d68", + "sha256:a3e8ee6386f6b68e2f818ccf422ccfafc59bcff10862b52c7819db3aa0352dfc", + "sha256:a7080928078ff56c07da393392f772054e93a39c8033dc9c04547bd949f9b614", + "sha256:a8ee3965e01f10e4ff92ba1727626328ab7ba93bcd5674ff2b78ea1048ee0cea", + "sha256:ab3f95f737fc1b258b8210308fc02ce1442cf5950cf6d30b09ad89ab9e8afebd", + "sha256:ac223ea031905319a64d0df116979ae438b42d6265862a3d63c9f9808c2905a7", + "sha256:ad3532291deedfdfcad3cf5d351d0076d646c68a0c63869a1bebf87c22159600", + "sha256:ad9185366893714cd4514ae5e1a098227704fa395cb41a7855d75968ecedc826", + "sha256:ae45853d25a23fba56681d2f9ed41f3e3f12f0a3b2393fefa08ff6406320a1f5", + "sha256:af2b808a79bb04075e87c81a5b6179365b93f9a851f29dafd67abff72085d0c8", + "sha256:b123f9d8702106f39dc3af63a031ca8b5279862a3747ec6c03ca49dbe78b71b9", + "sha256:b1399f918aea8fb76ffa99b474c9b768accea1f079fde407ee538bec89f20fa7", + "sha256:b916ff828d604d4311a5639b7b3da51eaea3923833ec3e300a5ee35eade99691", + "sha256:b9be297ffe1015bfb2db4a23b6e1fec7e48361cf7c7b4f4f6bbdd008451b0a7b", + "sha256:bc87bd34239835c8d73171acd039e591cea0a2ca8615e6188044ad170a40fca1", + "sha256:bd841dcf394ff261c26763a9d7be754176d4f1e6d26cb2a5d5331e91b6b56a5f", + "sha256:bdd70c2d9e73bca09fad54605dfeaae2b4e770b2800c65f5f5342901ed567b9f", + "sha256:c05b75ef3574c9ee4e05bbcf8513f7ccb155d426514be9efef5f6f53152d5f5c", + "sha256:c05d035e72530f6b00297941b0162218601542b76870c3bf2756bd87f16fc538", + "sha256:c19500343957f254ecf3e60174f4a4cdd21690e0e1677740a8017d28ab2a2a4b", + "sha256:c21e6a6e4e6d32fb6acbc4f0fa69e8319cac0d65eeaa8298d757371cc2a9c687", + "sha256:c2b246fa7cbdf9918488d1542a82bbb928cf71bcba66905c24131981e759ff0b", + "sha256:c2e97985641fe53ad7824d1b5bfeb7990a5ff788c4559ee44d7522642560ddc2", + "sha256:c3ae59461625518449f800acc4f874753ae96fcf993c47a27be07beb651371fd", + "sha256:c4abc425789f8540e86ca4cfdbc8dc650433cc2ac7c6136fee9bcb29d5665a02", + "sha256:cb4fcaabb28cabf21396a9b816b9afe091f8c776805fdf03e2cbc606b64dfa7c", + "sha256:cbce1b5a2a88a465d7865df61b7e4f8829ddc49b4fa841e9b241815c9d67c3b2", + "sha256:cf150693a51ca21e8288cd08a9de05e5ab331776dfcfd0537b14523338f0502a", + "sha256:cf81432281af66ba3285b26b09c2d478d84730dc50ff90926c1bdcef54048a33", + "sha256:d01029d54ff1f45c195b1f7e6fbf6e58fb7e7e12cda9a6e639570d9c581decb2", + "sha256:d06dbbfe8da01a0574de27afc19915bb3e7dddfbd184bb97e958f94051d8531b", + "sha256:d14d04058923d526a552fc3ebe8b0b0353c19516431cee196502b53119278fd9", + "sha256:d187f43d1c5b844adc871c5b8c22b4aa12a116aaca4e9bd1521bc9ce479aae1f", + "sha256:d1d084e92d0f4a096a5155b23d1ac603db8073ef98a3d1384badf1455e5ae742", + "sha256:d8354fbbe2abf0fb724b9303bef43bfd9b7a2779332813fa6d6559983954e7d8", + "sha256:dcbd1fe5083315243447c13f7252ae9fe9d128b1ae2857e3a916c609235dd863", + "sha256:dec0dafc116ac29a84d143fdbc3b83fdb5d4ed339276be2251154537ab30e14d", + "sha256:e15bb1535f68a27f28e579ba3a8f74e7b05d350c45f5622b76a311d5a19ae48a", + "sha256:e41f9d1d9240e8e0d8a670ad3e66c0c00f0b1f7150a31bc6c445a4f87c1cb3ba", + "sha256:e58acd43ac8d3905659c1d5309318dd576243e723dd7c3b5dd4f555479b77d4b", + "sha256:eadf95aab7301ac651628f097e521d742d0fb5f732155ebfd00d07933045d81c", + "sha256:ec786cb9d597dd75d993f8c1273e31bb2114c9bc22f67fba611e654e8347701b", + "sha256:ed557fa2744617eac3e34dd39b85037efbf23cd33f06851c00fdb8f18ad8f4c2", + "sha256:ee9913fa2b5bfa6111c11158cf481bb13544f8bd5d10382fb6ee2612b16fcf28", + "sha256:f0af2d2f745ec00a6616c4dfba4477f9156ccfb45690f6ffa5fd34f42e871ed4", + "sha256:f12d9690634414fc04b1a7072fdc35c34a9242232c1851fe4518383578bb09d4", + "sha256:f187030fc3d62c668feb0f09e92852e0eb414d7fcefc4748f2e67d245aade37e", + "sha256:f3abcabb04503023e1053add24472e78878de0bfd5c8a93686be01f4032c363c", + "sha256:f44107b5fdfecc03438feb165431652c8006650471326e53dd86d4c19124f5c5", + "sha256:f517a417cd02aa8fb05b603a0ac6d87b7b004c3ba4fcd279cad25cab7229043b", + "sha256:f8cc61ad94215ab98e5cbcdea2cfde45e11bb0136ee980e1240b721635e55790", + "sha256:f8df36f964c21168e345f914855e3b428e2cb6e1f739c8ef5963b77d86dd84ae" ], - "markers": "python_version >= '3.9'", - "version": "==2.46.5" + "markers": "python_version >= '3.10'", + "version": "==2.50.0" }, "pydantic-settings": { "hashes": [ @@ -2689,11 +2804,11 @@ }, "python-dotenv": { "hashes": [ - "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", - "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35" + "sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc", + "sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0" ], "markers": "python_version >= '3.10'", - "version": "==1.2.3" + "version": "==1.2.4" }, "python-jose": { "extras": [ @@ -3049,28 +3164,28 @@ }, "ruff": { "hashes": [ - "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", - "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", - "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", - "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", - "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", - "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", - "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", - "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", - "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", - "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", - "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", - "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", - "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", - "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", - "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", - "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", - "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", - "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284" + "sha256:0e271826af9a20d18c6cfae8c51e82959167c24859686ddd3eb9a7f0842ce81e", + "sha256:13ee90156522998c3037059d8f66885c8adeeaf7643bdce2caceee196ecd23e0", + "sha256:3d8cc360e666d1914e47b0777c6906d70cf18891a55532bd0a16844195d70859", + "sha256:3d8e4a002a94cd9d0dc48b51dc69d807a172b5b9bf2b668e656424dc5b55ead1", + "sha256:5cd03240d8208a557c2a9655a5cb07ebe36aa6bb35065f97d48c1f6adef5a322", + "sha256:5e50aa5b84decd9fe5b0bb0e6f71c3b592f1767ed09faa4b7207d933961e35cd", + "sha256:5f0ca4a40f81403689c04f12966e22f44e329ae362072d8f1587b7bda87f603b", + "sha256:7bb08489e234876fa2da67ae3ea938e9a2156da80293e0e4365abd6973d98329", + "sha256:864b6c1acb6b0bccf94b5a3938a1531fd09aaca5e5659a2e7bf0f3cf2a685540", + "sha256:8ab76bcda86dfd28e13776cb5de3c7bcdcf1ae3d37ed761113d1a5a415dc134c", + "sha256:a330178bdffc4205dbf3bda11d93e059e388fd6546f8cdd304501a9160363c0d", + "sha256:bc73e7c133e82d55b5f15897b2a442d72c0cb4a0c886c46801ce3c247150b60c", + "sha256:c0b8a60c06a218c337e1161638d34757f83449243e2db161483ddf948e53ad14", + "sha256:c154c73ff43f9854395e24cac507af13078962e53d2b511605058d22af1fdb88", + "sha256:c3f268baf004aea944f040623327119527ea231af15f7fb7890e82cea0679589", + "sha256:cbf7149e0927dc3295d5d64679a4765576eef71b00782b2ae969ef82274d6bb9", + "sha256:d66de796b726c4801e05fa99a2a8d7a780e107be222486c304ab61765561e866", + "sha256:db4f74c533403ab70fe4007873f6ae0c9f94a8b03158cf48d78788e47cdbe399" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.16.9" + "version": "==0.17.0" }, "s3transfer": { "hashes": [ @@ -3098,90 +3213,90 @@ }, "soupsieve": { "hashes": [ - "sha256:49e9380d7d2905463583bafe285e818c7366a9ed7b3aee221c1ac79c905d8bc0", - "sha256:8596eb8967d744174820280fa62b4542a2e955bfaccca73ed8a13c6eb8e9b502" + "sha256:713d5c69f90ef84deffec0b9c6244796575e3c9fc9081cefb96091ce1200a308", + "sha256:dd1d6bd1ba7cd39866edd704321c11b4df5e7ae98620174a9071a9af3a9c05cd" ], - "markers": "python_version >= '3.10'", - "version": "==2.10" + "markers": "python_version >= '3.11'", + "version": "==3.0.1" }, "sqlalchemy": { "hashes": [ - "sha256:0220e0d1d1ad2391a03cd6713ca5d795419033cc40baa1233f9ad454dffa25e6", - "sha256:054a2c2b6cbe3ca7cb88aba9c5e2ecdbfc43656bea22326a41d09e5398c86ec4", - "sha256:0593ed87886c44e94dfac4b22d20ed88b19a7dacd7440177d7156f9b011e4376", - "sha256:092a743f28f3a660321e9b093144b6a5b16f08dbb9b2f52466b47a2a128475c2", - "sha256:0b91e020ba33c0cb9842be6eb7de32dfebd9ad1e8f9a660a0fdb567fd0aeaed1", - "sha256:0f94929be35def6580cf161f20c71930d9364d62f7040b6aed02b13fd3ca09e6", - "sha256:0fad3dc37851b096ef72538b18152a5c1ae9c8b2013cd89f51eca83fc6eba36b", - "sha256:1477f2f1f531697753a729dd8f3e4e98f7019086f001fdc4d50350c929871c8c", - "sha256:18148691d761ef20a92cfc7dced305834e2c6ab4f4f3648f66f5d4028e234089", - "sha256:1abf1acecd4d0fc34d4ab7e9cda9e849ce95b647e1a5f76db7d2a5a0bea4e1e5", - "sha256:1ef7bc38d140b3cf08cc6e8823b8e76da2e188b5594997e2c16586c9778af472", - "sha256:28b882509319789d52966d70164ccfc5bc1aaffc1c7f98a79bbccec13346ebd7", - "sha256:2f0bb0502de28baed425278d4bd98658d8256419fd9ad6ca964138710f933aa0", - "sha256:3353333f8c25fc5e0661f54630238a17b0d6600305e224400cd4642cce4af34f", - "sha256:359e2411cf4713b4b323b3c2558a07663a19ab82a5d7b23c601e1be5432aac0c", - "sha256:389e7098e9ae15094f222496de98b0fd211c06b896c73c6e46aa9ca5b6fcd590", - "sha256:3ac9781474e4b6c3e184487643d47408666be794d5782165a817da63f981f89a", - "sha256:4357c1a222e141662251a59d3702f9b124294941171dfa6bd570513e9f4905ee", - "sha256:44dff425e7d88b04497b4317478a6af2a694327a087e6f1670a4266180838269", - "sha256:4789593e8979d7c946da1985a560200b6ef6c3dc6dcc5442985d62c6f2908875", - "sha256:481d926922d9bff632955ed6f07e2b87066b0d1069eb67cbc8c35e27f7fe5e1e", - "sha256:5516bdaacf5521b7de69a5c76961fde2cdc4edcb39730eccff16246a44cb8715", - "sha256:558d6d7d6eec0e54ed212320a587bb7f30553550396226d8488803c93757f17b", - "sha256:57ae70935cd382cd41d84757e802687f7c5e7411aecf2b962b7bdb6ea74e088c", - "sha256:583038541d9246dbb05fc4ab73038dc61820983c12fa5e32a3b0d903e1ba7bba", - "sha256:597e1edf9cbe6ce63974813000c02c7a44da3d21f1da47f07174732ba188e8ae", - "sha256:61333b2f806f64a08a1edf0b0e4271905f73c1f7e525c23da7a491f74c109835", - "sha256:63c0291bf0f721543db3719c71f2d5ad6649f12abbcbc8324d42805dac112cbc", - "sha256:6a261e2244fa88e6eded95a078550c0584da55db22d49b0b2db8c075788282fb", - "sha256:6a7ff6a02a102e0575d237172e90adc96466e6ab6511eae7fd43775e6ab19db2", - "sha256:73743f135b489d3551eeb3cbbb3756d6cc41bb68e033d2b7b2b620b11c6c9d12", - "sha256:73b0f7cb41a3df79dc0427377a9e2a099eb15995a327120d651f21309faa662a", - "sha256:758d52653c8902baac25269c5b7c6fca4a198e066f2212fd2ed1b98174fd7bbb", - "sha256:7c60fb6793cf60b2733012352f0e1861c38b24727d0e37a1da1f3974eb79f0ed", - "sha256:7ed18c0747eec4a0d8d9abe4c89d96ec0eda97721ab7d3a31d0c1ebcbf38cd2e", - "sha256:7f15820c84b2377bcf02cf754232690ac2ad90d425978bf1e5ba30cc04f94d55", - "sha256:842a5c00ca9719cc26fba6f401f70e4bac19dc8cc4dc36350803a03954d6aea2", - "sha256:87d45175e8ba4a1ee027ad9307e6a4bf8a242469415f12afc9477def739474b0", - "sha256:88cb0a27ee9571fe339d7809a40b14ff2387ea05d02629a390a03c87cc981277", - "sha256:8a3126c1203cdfc0f00884f9e8dbe95307b813f883ee9d9691983a563ebc96f8", - "sha256:8b0660ded5f9c090f9bf49fba78d52843ff3ca8c10f71624811f42f11655762d", - "sha256:8d846615b89702ef5d0809e701ff5f3816b88dd02670c777adc456f6d81ef3ef", - "sha256:9324681cf8d8e9c7f6e507fea6a8ac2215fee253340d4399645216a08b936660", - "sha256:9dcedb4b1ad160a1297772162f9a15fc317627b76ac7c98d12c41ce38522d8ef", - "sha256:a0421f9eedad616b04a097ae3f155c5a0a31380364369861fd6e0f46074c3fce", - "sha256:a74c46cd93e34b912adafab98aa71c862518451a019d97f9c2f37392fc8eea3f", - "sha256:abd893deac6b0765f51d87129c5631cc5bc8f784f8bf22a9d3903695c764bccc", - "sha256:af189bd861bd31a84f0ba244d6db0c8aeae9e56448c9001bf02ee6a93758b39e", - "sha256:b04365ffe57cdeaf4d0f75630332d857dde3f91336bc4bdec76c810473c9c35e", - "sha256:b2a036e3c8b183f9c31df7611e03a4bd160e055915772bb8c13ec382da8f3571", - "sha256:b357598df0676accd1ccfb6bbc5db53dbfb499052d2d1677adf4ce14a7180664", - "sha256:b76d8fddf080c53f2afdea6e7149721e7b980086a2f17a9b0bdb42571a92c285", - "sha256:c19633f7614e1af93192f89debfa5bfbfaed93c80dd8903417d1605d019958dc", - "sha256:cf88881d593f527860fa7c63a50095496a5fa15632b1fee58e3114353f7fbc22", - "sha256:d111318e32b4452d85b491d2ba3f39f6be2736ff858da616f0e6627bb3810145", - "sha256:dabf6a73f2cc1fa2155b29f2a8a85afd82d8613262e6054cc3c932f5dee5d474", - "sha256:dee7886f4725c304641929a38d1ebe163f9fb45a5c9363f34bbb85bfdcb084c5", - "sha256:e08c92feb447b60e352c3581cc8a03119e36917b79362af62fd1a289c4d8e27b", - "sha256:e1986faa95fd7b206e5bdd1af7f4ba9ac10f1166225fb2eca0fbca04e3851f7d", - "sha256:e20233c7d000c006a03f3734d8e7a6e63aa878fcd3482c1f412c864a0992e5a0", - "sha256:e37b1fe2e080bf47f3b693dfbec9aa39d3c3d52bfac3b4fef290a449570bcf3f", - "sha256:e5274d07ae1dc5dc0e452ec24bdc97cf76ba189cab3f0c1c0386a1523240f775", - "sha256:ea049f3e2702967414bc79f58a484dab191359267726607e6c1579a09ef93788", - "sha256:eb2095d39baecbf8b78ea202d3d21178cc8d73e52c3b450c82feb200d5b0d1a9", - "sha256:ee446c07db92e94af30e1390ef7d65c1172756403f743fb7d8b4fc5f377cd427", - "sha256:eef01acb10295adfac8a960f716687052fe16f14a3f5955b56c24eefa9df7851", - "sha256:f083b4b6a6d0061b0f9ffec0ea9ded82700a6bd11ced32ccb42f855f7806f812", - "sha256:f2c1c27a41d1a26db380b3bad45f822d979c9608108c32ee613a418ec454ecbf", - "sha256:f90e517b2eb9c4c40fcc978c8c242789fa6cb0064c501fd8c1515145ed5fe23d", - "sha256:fb1f36ca818146b0e844dbde6dced65805d4ee06ce6282aa46a807f9a6619ede", - "sha256:fcf3cbb33bb23bad75d2150157c21804618745731e9931ab27e61879a9f6123b", - "sha256:fdef70d59afce463bedef54580ba6a832f9f2313c1d0054e15cbb4ff46f37a97" + "sha256:07c60abaffb980b7382f2c75be8a5279c2b5df2626a0f5d751dd942799bf3b5c", + "sha256:080f8d853aac5bb5620f0ae6f46527397cf18dce0ec2b478b478469ef3cae2c4", + "sha256:0970394ec5d9e397aafc5bc5fa2b7f8b58cb191f2703006b19a96ef4bf00b8d9", + "sha256:0a9a464bc360856b7ea9bf8aa26aab92ca115dd08149cb0e004063d5db13584b", + "sha256:0b96edcc2cd60fe1e35f67a46f4eb076e57297841b9eae949ac5f196593f00a7", + "sha256:0d1ca95e42ce3c18818f170b741d30a33b292c6f6b9a202ffd717e28fc99b8c7", + "sha256:0e01a3e199ae219381c4889993c5584b1b905fffe6830f639adb6770036a8913", + "sha256:0f672ed6972164fec94a8f0b21dcf8545080d0727866335fb8adf9f4764ce6ec", + "sha256:12642e105b4e0cb2ca8428037368c1cbcded7b9d0344174607174d82b700e1eb", + "sha256:14528d37d7d46a92f2a483f188f7fecd86cdd789254a0412b960c9fc5e9efd6d", + "sha256:1541ba5bf0f232cd61f9ef3df78c93977c72ba6031506a0e6d057b2a3ddb76e9", + "sha256:1ac64fce94c5b389062d2e3806db5dc780447591e0dfd5ead218c884f0703f2e", + "sha256:1d66fdcc5506e0f8bb8d3f4f95125220a7cd6c46e8b1762750f01e9639973dd8", + "sha256:22129e7d00ac66b291840c4dc83a9c497456ab5bffa682dcbfdc2356f9e49e5a", + "sha256:283914efed30e4d44301e36ac90ad048570538b8a70f072fe01578d9b205d09c", + "sha256:2e1b5343d315b10a4a71da481729f66f830a561595e02b61e8a5a65d658325ac", + "sha256:308f96d24e773d64609a2a0d1161a068f9f6e9165523bc4e07aa9c45f0c4213f", + "sha256:3341ddc430733cd961bc064889f42712a0b4056733a21c83176842aad67d12a6", + "sha256:343a0493a81278bfe30be1ec81214a55f2f44aaa4662d230be359ab2aa18cc2a", + "sha256:346d144e8912ae087b10d3c2081657cb634728600693eee6dbb71d7eb4768101", + "sha256:3c998d70e60fc95e93e5971395818c50f8a34396a6352075256fefac6b5cf81b", + "sha256:3d2eacdbeb990b80235763860923c60a8393745b66f7149a734980c65896da72", + "sha256:3d675b0856b6703b29d023517a4c19fecfbb55214ff5c72cd813527e40aed9b4", + "sha256:3e5045fb6aadbb0f978ab9b9d8822f7b7a97d2281814e7d13d791155664eace3", + "sha256:3e5de57c71b3460e2ca6137e82cd3cb8c9f711f301f50d5c77156fdb9c822999", + "sha256:3fd608a06bafa768ad5711df4e17eb058bdc490e9df7d39b12a90947471e8712", + "sha256:418786f05387ddb66ee683a1d016c5a8d9bf7be921e6ee8f285c7b6ac961a731", + "sha256:42c37c06adcecf444e8c981f7e9237a41bdd445c83da0df9e08b4ad958becbbc", + "sha256:55072780d1aae84dea443ce27edeb745f6cc4d19ad89416abbb6b49712080e7c", + "sha256:596a95611c217cb19c21f02f43c637cb507cab71dcf0467c5c7d98fcdd703007", + "sha256:6005f2f5fcd67fdd721446128e6a2a1d18f77387a604fbd26b0006a086b33096", + "sha256:61a2c48771cf314b6613d327c795902bbc0eb6d6169deb23b35004ba6ad6cc0d", + "sha256:63dc25b21fd9a41dc09b7aada4b3b0d97cf4b6414f74bced6ac45326bc799ac9", + "sha256:64d41be1dd88f184de1931f0173f4827122a1b49fd1150656641200c0bdf640c", + "sha256:6929a11ad26a91a4efd891c1252b373c2e88f056910b83ec6030ed3f2cbcb734", + "sha256:6c79e0c824d51c586757ecd342160bbdede9010df04bb71b9bbfffd5c7b6ee29", + "sha256:70006e9e6157200b795beeee04bd5cb15bccb40a14de595eb9f5dcf5945ed244", + "sha256:71040390ef01c85e9d26e5c83cb0c5942dcc8725c49186430af160ce2f54234d", + "sha256:72e3fa41d1fdab87d4e88bbdd69c9522e2795549fbe7b07bcf4ae9ec175f4b11", + "sha256:778094c83e36c430756a7e1a1ac66fc3cffb2c6a1067958fe6b920abcec7bc5a", + "sha256:7a2f6164c0527cd8fc4cea79a5c9d8369ffee417b8ba444a42342f36b91deb75", + "sha256:7b3f58bd26fc010ea28976d401845e4e6ce02e1b7c0288b3ea9c9a3c396f0bcc", + "sha256:7bd7ad604487daa7eab8716471c29a7185f17b5287ce73bb7bc79fea050d8cfd", + "sha256:8080022e101afb17565dc5a358a165ff4a20cd97b20b4db49ebed66315b3c733", + "sha256:81f802c96dbf96e59c6982fa1b87da7868920fb0c27b9b81e560a62f57c2ccfb", + "sha256:82d728075d42bd457d09655cf22e99d772a648c6f67e86743a4f05b7d063ca18", + "sha256:84272f329c15081a1e09b4a7261118b4e8a547f43e00fca98e55bbdf19eff3be", + "sha256:89db94855287fdac98d74595cf13ea59fbffa608d6400ff972b0fd4c036d873f", + "sha256:93b9416b9011a3b7689a933e04ac9f61d15686b6cb1948ebc1f41467153116c3", + "sha256:948dff080b5ac00c8e63bf9e59fa70e386cca1476f55c672a72b6ec12e5cdb05", + "sha256:963348422b22f760e9462e56bc32bf4d95d224cc5b8c79a3c6e3b786d3d2a2b2", + "sha256:976bd3fecfcfa58d69eab67e76325f564ed775aa0c0accf138ae17324b461431", + "sha256:98f7a4bfeaed3722804f737ae2bd4077b35e57d6f4531fe612bac8160cda5acd", + "sha256:a0bb9ee6a38cb36240dc88da11888348f61506047be54de3f09496c3b0ead6f5", + "sha256:a577e2127e52b0fe2bc54c73abb375a20ffe6f59fbc5568ccafc233f5bfcf8ef", + "sha256:a64d54015233f824f171009977bfbb6b08bd0347b700cf17cb047ffb94c4148f", + "sha256:a6d147c31e189541ae7cd990482c4f960f9e8abce186551225fa355856dbf1a5", + "sha256:acf8982c70471a68aa90d1aba08b48860c55b3357ec84ccb0f09368ead2ce099", + "sha256:b756d74527c56a7e4cfae297f7930c1d75bdf4b23f214c8c13779746d28060cb", + "sha256:bab7f51d38766d6a64da2b41976f1b3f9cc2ff37d3f2f63bdbac876199f3a48e", + "sha256:bc33d3e59d4e84b8866cc9ba13732585e37212dbe3542cb09f232682b36f47a5", + "sha256:cb2cb98d056e63e353ed697750004e07c79b054d73059ba3184ca3bb07296bea", + "sha256:d045e63095828d2f1fd84d499936e6791522c15c390373fc755f118e4040393a", + "sha256:d2cb669c6bd1f19caf51db6e3c4fdd4cbb76f9db3ef81c3aeb5e288d9bae101b", + "sha256:dffa69d2f3ba1933c1c1882dbef8fb3231b33eb19263e8b8c5cea24995071f06", + "sha256:e2ace725a430e5b303fc3c422196966328ce77fb4fd053ad85572b46ed5fb71a", + "sha256:e30524ae24e31d83e1b5f734862882c442f4158e3566f2c5f5e9bd3c659bb517", + "sha256:e3a026436c51f296aa1d01243909a3b76490950e927824b10899a083cc26e7c3", + "sha256:e43fca5fdd5f34a3f8c54107a3648d3139de8bbf596a189f3f0de94bd84949bb", + "sha256:ec5d079935f67febe0ab8a3a203ad591b99508adc34ae0027f696dcb20373537", + "sha256:f953be9ba26039a24a5205c65d33518b608ce6f4f0f4e9b9c14eaf42a10dfc52", + "sha256:fba3500e170d25f581e053009edeb0b158116084d91d465de218718d336b67c3" ], "index": "pypi", "markers": "python_version >= '3.11'", - "version": "==2.1.1" + "version": "==2.1.4" }, "starlette": { "hashes": [ @@ -4146,11 +4261,11 @@ }, "filelock": { "hashes": [ - "sha256:a93c4d93269b339a6af4848342c7e940d0f9928ad95eff64764699e5f1bf8a6a", - "sha256:da5915714a70b55d167fdc7e251ad91302b0a36816fb574dfafae8f4f2c9bb21" + "sha256:2ce9818e3e2d8f284c1a964414447ef148d42a5fd5e2a477a7118e574b293ec1", + "sha256:ad7f724afef953e731b1cc39bcd3a09166d72ed7fcdf29e6e88b1c3235c6715d" ], "markers": "python_version >= '3.10'", - "version": "==4.0.7" + "version": "==4.1.0" }, "identify": { "hashes": [ @@ -4162,12 +4277,12 @@ }, "iniconfig": { "hashes": [ - "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", - "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", + "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.3.0" + "version": "==2.3.1" }, "nodeenv": { "hashes": [ @@ -4188,11 +4303,11 @@ }, "platformdirs": { "hashes": [ - "sha256:29dbf06d96c500bc6bdbce75fb0a14d63279c93b1842f97e72a135b33e856983", - "sha256:eab5f70271a490ef74618bb314fbb86e3c7e82fa3b9c922c2ea0e0a1a155d329" + "sha256:1aa0b0d3f224c1f07c295121e312a5a24a180d6ae5a8425ea1784b3e3863e9c0", + "sha256:3dbcf4cd708f21cf876c4eaa90e58412bc4f033d87143f41b1493ff77c25b7e1" ], - "markers": "python_version >= '3.10'", - "version": "==4.12.2" + "markers": "python_version >= '3.11'", + "version": "==4.13.0" }, "pluggy": { "hashes": [ @@ -4239,11 +4354,11 @@ }, "python-discovery": { "hashes": [ - "sha256:cf87d3627dfb4412437fdd5b13eae402607722998d21567993aedbc59b23c15e", - "sha256:d43fcdef879fe795352bd13ccf8d185ba5a9f86f36cfcd00529f596e737442b3" + "sha256:cd1738ca1d37c86ef9d0b654dd46fcee1e41c97c6add12575e8501ced39afdb4", + "sha256:f0c697f95a3aaec4174a6e5e58f5885e25768684bafc76f1afde384709ebdcf2" ], - "markers": "python_version >= '3.8'", - "version": "==1.6.1" + "markers": "python_version >= '3.9'", + "version": "==1.6.2" }, "pyyaml": { "hashes": [ @@ -4326,28 +4441,28 @@ }, "ruff": { "hashes": [ - "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", - "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", - "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", - "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", - "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", - "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", - "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", - "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", - "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", - "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", - "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", - "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", - "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", - "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", - "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", - "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", - "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", - "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284" + "sha256:0e271826af9a20d18c6cfae8c51e82959167c24859686ddd3eb9a7f0842ce81e", + "sha256:13ee90156522998c3037059d8f66885c8adeeaf7643bdce2caceee196ecd23e0", + "sha256:3d8cc360e666d1914e47b0777c6906d70cf18891a55532bd0a16844195d70859", + "sha256:3d8e4a002a94cd9d0dc48b51dc69d807a172b5b9bf2b668e656424dc5b55ead1", + "sha256:5cd03240d8208a557c2a9655a5cb07ebe36aa6bb35065f97d48c1f6adef5a322", + "sha256:5e50aa5b84decd9fe5b0bb0e6f71c3b592f1767ed09faa4b7207d933961e35cd", + "sha256:5f0ca4a40f81403689c04f12966e22f44e329ae362072d8f1587b7bda87f603b", + "sha256:7bb08489e234876fa2da67ae3ea938e9a2156da80293e0e4365abd6973d98329", + "sha256:864b6c1acb6b0bccf94b5a3938a1531fd09aaca5e5659a2e7bf0f3cf2a685540", + "sha256:8ab76bcda86dfd28e13776cb5de3c7bcdcf1ae3d37ed761113d1a5a415dc134c", + "sha256:a330178bdffc4205dbf3bda11d93e059e388fd6546f8cdd304501a9160363c0d", + "sha256:bc73e7c133e82d55b5f15897b2a442d72c0cb4a0c886c46801ce3c247150b60c", + "sha256:c0b8a60c06a218c337e1161638d34757f83449243e2db161483ddf948e53ad14", + "sha256:c154c73ff43f9854395e24cac507af13078962e53d2b511605058d22af1fdb88", + "sha256:c3f268baf004aea944f040623327119527ea231af15f7fb7890e82cea0679589", + "sha256:cbf7149e0927dc3295d5d64679a4765576eef71b00782b2ae969ef82274d6bb9", + "sha256:d66de796b726c4801e05fa99a2a8d7a780e107be222486c304ab61765561e866", + "sha256:db4f74c533403ab70fe4007873f6ae0c9f94a8b03158cf48d78788e47cdbe399" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.16.9" + "version": "==0.17.0" }, "the-agent": { "editable": true, @@ -4355,11 +4470,12 @@ }, "virtualenv": { "hashes": [ - "sha256:6fd04089fc0dc33549e7abdff70fc3b63d4e15799f2dbf3281f80d13b9fce522", - "sha256:719b189804e66678017d9f63bbfc590c44f6b96ab4829513806394f9b375929c" + "sha256:b0651e0174982bba17cc6f0aaa2ef496730cb85a3f35eb5d1119d1c904f7d1da", + "sha256:c4cb6c13e46b57225a999c7e22a09b163393878facc7ac4c059a57f46faa1647" ], + "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==21.14.1" + "version": "==21.14.5" } } } diff --git a/alembic.ini b/alembic.ini index 20e2f7b5..61ec9872 100644 --- a/alembic.ini +++ b/alembic.ini @@ -12,7 +12,7 @@ script_location = ./src/db/alembic # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. -prepend_sys_path = .src +prepend_sys_path = ./src # timezone to use when rendering the date within the migration file # as well as the filename. diff --git a/docker/.env b/docker/.env index 6cdc7b60..0154dee2 100644 --- a/docker/.env +++ b/docker/.env @@ -1,20 +1,35 @@ -# Minimal configuration for local Docker testing -# For production, set proper values for all secrets +# local-only example values; replace secrets before deploying -# Database (required) -POSTGRES_HOST=db POSTGRES_DB=agent -POSTGRES_USER=root -POSTGRES_PASS=root -POSTGRES_PASSWORD=root +POSTGRES_USER=agent +POSTGRES_PASS=local-db-password -# API Authentication (required) -API_KEY=developer +SHLINK_DB_NAME=shlink +SHLINK_DB_USER=shlink +SHLINK_DB_PASSWORD=local-shortener-db-password +SHLINK_PORT=8081 +SHLINK_DOMAIN=localhost:8081 +SHLINK_HTTPS_ENABLED=false +URL_SHORTENER_BASE_URL=http://shortener:8080 +URL_SHORTENER_API_KEY=local-shortener-key -# Path configuration (required for Docker) +API_PORT=8080 +API_KEY=local-api-key +JWT_SECRET_KEY=local-jwt-secret-change-me +TOKEN_ENCRYPT_SECRET=local-encryption-secret-change-me +PUBLIC_API_BASE_URL=http://localhost:8080 +BACKOFFICE_URL_BASE=http://localhost:5173 +WEBSITE_URL=http://localhost:5173 +PARENT_ORGANIZATION="Example Organization" +THE_AGENT_ISSUES_REPO=appifyhub/agent-backend THE_AGENT_ISSUE_TEMPLATES_PATH=/app/src/templates -# Optional: Override defaults if needed -# LOG_LEVEL=debug -# MAX_USERS=100 -# WEBSITE_URL=https://the-agent.appifyhub.com +# leave S3 and Uploadcare unconfigured to use the persistent local attachment volume +S3_BASE_URL= +UPLOADCARE_PUBLIC_KEY= +UPLOADCARE_PRIVATE_KEY= + +# add provider keys and integration credentials from src/util/config.py as needed +# PLATFORM_OPEN_AI_KEY= +# TELEGRAM_BOT_TOKEN= + diff --git a/docker/README.md b/docker/README.md index 3a33bec8..14178859 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,35 +1,37 @@ ## Docker Compose support -This project can run in the Docker Compose framework. +This project can run in Docker Compose with its database and URL shortener. Attachments use persistent local storage by default, so a separate object-storage service is not required. -The basic `Dockerfile` is available in the project's root directory. To run the service using Docker Compose, you need a `docker-compose.yml` file, which is provided here. +### Start and stop -The basic command to run the configured image is: +Run these commands from the project root: ```console -$ docker compose up +docker compose --env-file docker/.env -f docker/docker-compose.yml -p agent-local up -d --wait +docker compose --env-file docker/.env -f docker/docker-compose.yml -p agent-local down ``` -When you don't need the service anymore, you can just tear it down: +If Compose is installed as a standalone binary, use `docker-compose` instead of `docker compose`. -```console -$ docker compose down -``` +Stopping the stack preserves its named volumes. Adding `--volumes` to `down` deletes the stored data. ### How does the local Docker build work? -The build copies over the necessary files into the image, then runs the run script from within. Depending on how your storage is configured, this may pull the necessary dependencies before running. - -Docker is not a development environment, therefore no tests will be executed during Docker builds. It's assumed that you're running Docker as the last step of your development process. +The basic `Dockerfile` is available in the project's root directory. It copies the necessary files into the image and installs the dependencies; tests are not executed during builds. -The latest image is available in Docker Hub and will be tagged with `appifyhub/the-agent:latest`. +Compose uses the published `appifyhub/the-agent:latest` image by default. ### How do I reconfigure the project properties? -The directory here contains a `.env` file containing configurable build properties. There are sensible defaults that you can change by simply editing the `.env` file. If running the project from your machine directly, normal environment variables will be used. +This directory contains a `.env` file with generic local examples. Edit it to configure the stack and supply credentials for the integrations you use. + +Exported shell variables can override these examples. Keep signing and encryption secrets stable when reusing stored data. + +Database initialization runs only for new volumes. Back up existing data before changing database versions, credentials, or storage settings. ### Word of caution -It goes without saying, but let's say it anyway: +These defaults are for local testing and bind exposed services to loopback. Remote integrations need publicly reachable URLs. + +**DO NOT** deploy these example settings directly to production or production-like environments. -**DO NOT** deploy any of this directly to production or production-like environments. All of the files provided here are for testing only and should not be used when deploying to real environments and real users. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 34b67c21..b778be79 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,32 +1,80 @@ services: db: - image: postgres:alpine + image: postgres:17.9-alpine container_name: the-agent-database restart: always - env_file: - - .env + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASS} + SHLINK_DB_NAME: ${SHLINK_DB_NAME} + SHLINK_DB_USER: ${SHLINK_DB_USER} + SHLINK_DB_PASSWORD: ${SHLINK_DB_PASSWORD} volumes: - postgres:/var/lib/postgresql/data + - ./init-shlink-db.sh:/docker-entrypoint-initdb.d/init-shlink-db.sh:ro expose: - 5432 healthcheck: - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 5s retries: 15 + shortener: + image: shlinkio/shlink:4.5.2 + restart: always + environment: + DEFAULT_DOMAIN: ${SHLINK_DOMAIN} + IS_HTTPS_ENABLED: ${SHLINK_HTTPS_ENABLED} + INITIAL_API_KEY: ${URL_SHORTENER_API_KEY} + DB_DRIVER: postgres + DB_HOST: db + DB_NAME: ${SHLINK_DB_NAME} + DB_USER: ${SHLINK_DB_USER} + DB_PASSWORD: ${SHLINK_DB_PASSWORD} + depends_on: + db: + condition: service_healthy + ports: + - "127.0.0.1:${SHLINK_PORT}:8080" + healthcheck: + test: ["CMD", "php", "-r", "exit(@file_get_contents('http://127.0.0.1:8080/rest/health') === false ? 1 : 0);"] + interval: 10s + timeout: 5s + retries: 15 + start_period: 30s + api: image: appifyhub/the-agent:latest container_name: the-agent-api restart: always env_file: - .env + environment: + POSTGRES_HOST: db + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASS: ${POSTGRES_PASS} + URL_SHORTENER_API_KEY: ${URL_SHORTENER_API_KEY} + volumes: + - attachments:/app/.local/s3 depends_on: db: condition: service_healthy + shortener: + condition: service_healthy ports: - - "80:80" + - "127.0.0.1:${API_PORT}:80" + healthcheck: + test: ["CMD", "pipenv", "run", "python", "-c", "from urllib.request import urlopen; urlopen('http://127.0.0.1/health', timeout=5).close()"] + interval: 10s + timeout: 10s + retries: 15 + start_period: 30s volumes: postgres: driver: local + attachments: + driver: local diff --git a/docker/init-shlink-db.sh b/docker/init-shlink-db.sh new file mode 100755 index 00000000..6d6bfd22 --- /dev/null +++ b/docker/init-shlink-db.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \ + --set ON_ERROR_STOP=1 \ + --set shlink_db="$SHLINK_DB_NAME" \ + --set shlink_user="$SHLINK_DB_USER" \ + --set shlink_password="$SHLINK_DB_PASSWORD" <<'SQL' +CREATE ROLE :"shlink_user" LOGIN PASSWORD :'shlink_password'; +CREATE DATABASE :"shlink_db" OWNER :"shlink_user"; +REVOKE CONNECT ON DATABASE :"shlink_db" FROM PUBLIC; +SQL + diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml index d9376d7b..fbbb76e9 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.38.4 + version: 5.39.1 license: name: MIT url: https://opensource.org/licenses/MIT @@ -1325,6 +1325,10 @@ components: type: boolean nullable: false description: Whether to apply this user's custom prompt instructions in agent interactions for this chat + split_messages: + type: boolean + nullable: false + description: Whether to split this user's replies into separate messages at paragraph boundaries in this chat; platform message-length limits still apply when disabled max_output_tokens: type: integer nullable: false @@ -1343,6 +1347,7 @@ components: required: - use_about_me - use_custom_prompt + - split_messages ChatSettingsResponse: type: object @@ -1428,6 +1433,10 @@ components: type: boolean nullable: false description: Whether this user's custom prompt instructions are applied in agent interactions for this chat + split_messages: + type: boolean + nullable: false + description: Whether this user's replies are split into separate messages at paragraph boundaries in this chat; platform message-length limits still apply when disabled max_output_tokens: type: integer nullable: false @@ -1446,6 +1455,7 @@ components: required: - use_about_me - use_custom_prompt + - split_messages - max_output_tokens - max_chat_history_depth - max_iterations diff --git a/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/.openspec.yaml b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/.openspec.yaml new file mode 100644 index 00000000..81264324 --- /dev/null +++ b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-09 diff --git a/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/design.md b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/design.md new file mode 100644 index 00000000..8c137a9f --- /dev/null +++ b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/design.md @@ -0,0 +1,84 @@ +## Context + +See `proposal.md` for motivation. `ChatMembershipDB` already stores settings under the `(user_id, chat_id)` key. `ChatMembershipRepository` accepts and returns the `ChatMembership` dataclass through `chat_membership_mapper`; persistence changes therefore require the matching dataclass field even before domain behavior changes. + +`ChatMembershipService` creates memberships and reconstructs them when admin status changes. The settings controller also reconstructs memberships when saving per-chat settings. Both must carry the new preference explicitly once their milestones are implemented. + +`DomainLangchainMapper.map_bot_message_to_storage` joins AI content blocks with `CHAT_MESSAGE_DELIMITER` (`\n\n`) and splits them while preserving code blocks and consecutive list items. Its two production callers are the normal/command reply path and burst-level error path in `MessageBurstService`. The burst worker already establishes the triggering author and chat as the DI invoker context. The platform SDKs send one text message per call and perform existing delivery accounting. + +The settings frontend is in `../agent-web`: its `UserChatConfig` interface, `ChatSettingsPage` dirty-state/save logic, and `ChatSettingsForm` toggles are the existing integration points. This change's current OpenSpec edit scope covers only the backend repository. + +## Goals / Non-Goals + +**Goals:** +- Add one durable boolean preference without a second settings mechanism. +- Preserve paragraph text, existing enabled-mode code/list behavior, and explicit user choices across synchronization and settings saves. +- Complete each architectural layer with its tests before requesting manual review. + +**Non-Goals:** +- No pricing changes, template-message work, accounting changes, global user preference, chat-admin setting, or automatic inference from pricing. +- No prompt rewrite: the same blank lines remain paragraph formatting when splitting is disabled. +- No changes to independently sent notifications, generated media, or reaction handling. +- No automatic migration generation/application and no frontend edits from the backend-only apply scope. + +## Decisions + +### 1. Store a non-null boolean on memberships + +Add `split_messages` beside the existing membership settings. Use the existing boolean model conventions with ORM and server defaults of true. The dataclass also defaults to true so the generic persistence representation remains consistent. Platform-aware initialization uses integration policy, not a database default that would need to query another table. + +A nullable override would require a third state and resolution on every read without offering a requested benefit. A chat-wide preference would not meet the requested user–chat ownership. + +### 2. Treat repository tests as the database milestone + +The first task changes the DB model, the minimum persistence DTO field, DB/domain mapping, repository upsert, and existing shared stubs. Extend existing save/get/upsert cases in `test/features/chat/membership/test_chat_membership_repo.py` with the new field alongside other settings, retaining existing membership-key coverage rather than adding a flag-only isolation test. Do not create a database-model test file or assert through repository SQL/session internals. + +The dataclass field and mapper are necessary persistence plumbing in this milestone, not permission to implement domain defaults or delivery behavior ahead of review. Mapper regressions cover both stub defaults and overrides of every field, explicitly asserting all nine values in the DB intermediate and domain round-trip result. These complementary cases catch mappings that hardcode either default or overridden settings, while repository tests separately cover persistence and isolation. + +### 3. Initialize platform defaults once and preserve explicit choices + +New Telegram memberships start with true; new WhatsApp, GitHub, and background memberships start with false. `SPLIT_MESSAGES_DEFAULTS` in `integration_config.py` selects each platform's default, and `integrations.resolve_default_split_messages` resolves it for every membership creation path and the later missing-membership delivery fallback. Membership services do not encode platform business policy. Existing membership synchronization/admin-refresh paths carry their stored value rather than recalculate it, including after integration defaults change. The migration freezes the original WhatsApp-false/other-true backfill once, joining `chat_memberships` to `chat_configs` by chat ID. + +When delivery has no stored membership, resolve the same platform default locally without creating a membership or doing a remote authorization call. Use the triggering author, consistent with existing invoker settings and delivery payer ownership. + +### 4. Keep splitting decisions in the existing reply mapper + +Pass an explicit `split_messages` argument to `map_bot_message_to_storage` and migrate all callers/tests when the domain milestone changes that signature; do not add a compatibility alias. Enabled mode uses the current block-preserving delimiter split. Disabled mode uses the joined content as a single logical text message. Keep empty-content handling and unclosed-fence handling consistent. Burst-service regressions verify actual enabled paragraph deliveries and the burst-level error-notification caller through a one-shot shared external-fake failure, without mocking the mapper, service, or SDK or pinning error-banner wording. + +Provider-required length splitting is separate from optional paragraph splitting and applies to both modes. Implement it within the existing reply-mapping path, not by changing SDK return contracts or introducing a transport abstraction. Prefer paragraph/newline/word boundaries, with a hard boundary for an oversized uninterrupted span; preserve text order and all user-visible content. When a code fence crosses a required boundary, close/reopen it as needed without exceeding the provider limit. Validate provider-specific length semantics, including Unicode and Telegram entity parsing, before implementing boundary logic. + +### 5. Expose the boolean through existing settings contracts + +Add a required boolean to `UserChatConfigResponse` and `UserChatConfigPayload`, using the existing full per-chat user-settings payload convention. Update the controller and response mapper without changing authorization: any authorized member can edit their own preference, and no chat-admin permission is required. This is a coordinated API/frontend contract update; missing/invalid values use the existing validation behavior rather than a silent default that resets saved choices. + +### 6. Apply the frontend milestone from this change + +From the sibling `agent-web` repository, use this backend change directly as the frontend milestone's OpenSpec source, as selected by the user. Add the field to settings types, dirty-state detection, save payloads, and the existing toggle form. Reuse its localization and toggle conventions without adding a checkbox-specific undo interaction. Present the option as a natural chat flow: several shorter messages in quick succession instead of one long reply, with a warning that per-message platforms may cost more. Verify loaded platform defaults, dirty state, and save/reload on the actual page, including a non-admin group member. + +### 7. Leave dependency upgrades until the final milestone + +After the companion UI review is approved, upgrade all backend production and development dependencies through Pipenv, updating declared constraints and the lockfile together. Use the latest stable versions compatible with the project's Python/runtime requirements and limit application changes to required compatibility fixes. Check open backend Dependabot PRs and post `@dependabot recreate` on each, recording the PR/comment links or that none are open; do not merge or close them. Verify the installed environment, offline application startup and relevant settings/reply paths, focused/full tests, and repository-wide quality checks. Stop for final manual review before archiving either change. + + +## Risks / Trade-offs + +- A new ORM column requires a migration before code is deployed against an existing database → keep generation/application user-controlled and block rollout until the migration is reviewed. +- A true server default alone would leave old WhatsApp memberships enabled → explicitly backfill by platform in the generated migration. +- Reconstructing a membership can reset a false value → exercise synchronization, admin promotion/demotion, and unrelated settings saves in their owning layers. +- Combining paragraphs can exceed provider text limits → implement and exercise mandatory length splitting; do not promise an unconditional single message or truncate content. +- Required API payload fields need a coordinated frontend release → finish and review the companion UI before product rollout; no compatibility shim is introduced. +- Milestone completion is not feature completion → mark only verified tasks complete and stop at each review gate, without claiming reply behavior has changed after the persistence task. + +## Migration Plan + +1. Implement and verify the first persistence task against isolated SQLite; no live database is touched. +2. The user runs `./tools/db_generate_migration -y` after reviewing the model change. `src/db/alembic/env.py` already imports `ChatMembershipDB`; verify this remains true before generation. +3. Review the generated revision, include the one-time platform-aware backfill, and verify upgrade/downgrade against an isolated migration environment. Do not hand-create an Alembic revision in place of the required generation command. +4. The user approves and runs `./tools/db_apply_migration` before deployment. Finish the subsequent domain, API, delivery, companion UI, and final dependency-upgrade review gates before rolling out the product behavior. +5. Roll back by reverting application/frontend changes before removing the column with the migration downgrade. Removing the column discards saved splitting preferences and requires explicit user approval. + +## Review Gates + +At every backend milestone: run focused tests, a real offline smoke scenario covering the changed path, the complete offline suite with `pipenv run pytest -v`, and repository-wide Ruff/spacing checks. Fix only issues caused by this change; report unrelated failures without altering unrelated code. Update OpenSpec task status, report evidence and the exact next milestone, and stop until the user explicitly approves continuation. + +The initial apply is restricted to task 1.1. User-run migration generation remains task 1.2; do not mark it complete or cross into domain behavior before the user reviews the persistence work. diff --git a/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/proposal.md b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/proposal.md new file mode 100644 index 00000000..2d9d0aeb --- /dev/null +++ b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/proposal.md @@ -0,0 +1,32 @@ +## Why + +Blank-line-delimited replies are currently sent as multiple messages. Every outgoing WhatsApp message is billable in this application's operating model, so users need a per-chat choice that avoids unnecessary sends while preserving readable paragraphs. + +## What Changes + +- Persist a `split_messages` boolean for each user–chat membership and expose it through the existing per-chat settings API. +- Default new memberships to true for Telegram and false for WhatsApp, GitHub, and background chats. Preserve existing choices and the original reviewed migration backfill when integration defaults change. +- When enabled, retain the current paragraph splitting and code/list preservation. When disabled, retain paragraphs in one text message, splitting only when the provider's text limit requires it. +- Use the triggering user's preference for normal replies, command replies, and burst-level error replies in that chat. Keep emoji reactions and independently sent media/notifications unchanged. +- Add a companion settings checkbox labelled “Split replies into separate messages” in `agent-web`; implement that companion change only at its separately reviewed milestone. +- Implement from persistence upward, with existing layer-appropriate tests and an explicit manual-review stop after every milestone. +- At the very end, after the feature/UI review gates, upgrade all backend production and development dependencies through Pipenv and request recreation of any open backend Dependabot PRs. Verify compatibility and stop for final manual review before archiving. + +## Capabilities + +### New Capabilities + +- `per-user-chat-message-splitting`: Persist, configure, and honor a user–chat reply-splitting preference with platform defaults and provider-required size splitting. + +### Modified Capabilities + +None. The main specification directory currently contains no existing capabilities to amend. + +## Impact + +- Backend membership DB model, persistence DTO and mapper, repository, membership creation/refresh service, settings API, response mapper, reply mapper, and message-burst delivery callers. +- Existing repository, membership-service, settings-controller, API-model, reply-mapper, and delivery tests, plus shared stubs where needed. No new test files are planned. +- A user-generated Alembic migration and platform-aware backfill. Migration application requires the user's approval; no live database changes are automatic. +- Existing API documentation and relevant OpenSpec artifacts; no pricing or accounting-policy changes. +- Companion frontend settings types, form, and page in `../agent-web`. The frontend milestone uses this backend change directly as its OpenSpec source, as selected by the user. +- Backend `Pipfile`/`Pipfile.lock`, strictly necessary dependency-compatibility fixes, and `@dependabot recreate` comments on open backend Dependabot PRs during the final milestone. diff --git a/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/specs/per-user-chat-message-splitting/spec.md b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/specs/per-user-chat-message-splitting/spec.md new file mode 100644 index 00000000..5c0327ec --- /dev/null +++ b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/specs/per-user-chat-message-splitting/spec.md @@ -0,0 +1,125 @@ +## Purpose + +Allow each user to choose whether replies in a particular chat are split into separate paragraph messages, reducing unnecessary billable deliveries while preserving text and provider limits. + +## ADDED Requirements + +### Requirement: Durable user–chat preference + +The system SHALL persist a boolean `split_messages` preference independently for each user–chat membership. Saving or refreshing unrelated membership settings SHALL preserve the stored preference. + +#### Scenario: Persist an explicit disabled choice +- **WHEN** a user disables splitting in a chat and the settings are subsequently reloaded +- **THEN** the preference is false for that membership + +#### Scenario: Persist an explicit enabled choice +- **WHEN** a user enables splitting in a chat and the settings are subsequently reloaded +- **THEN** the preference is true for that membership + +#### Scenario: Membership isolation +- **WHEN** a user changes splitting in one chat +- **THEN** that user's preferences in other chats and other users' preferences in the same chat remain unchanged + +#### Scenario: Administrative status changes +- **WHEN** membership synchronization changes the user's admin status +- **THEN** the stored splitting preference remains unchanged + +### Requirement: Platform defaults + +The system SHALL default splitting to true for new Telegram memberships and false for new WhatsApp, GitHub, and background memberships. Integration default changes SHALL NOT overwrite existing memberships or alter the original migration backfill. Subsequent membership refreshes SHALL NOT replace an explicit stored choice with a platform default. + +#### Scenario: New WhatsApp membership +- **WHEN** a WhatsApp user–chat membership is created without an explicit choice +- **THEN** splitting is false + +#### Scenario: New Telegram membership +- **WHEN** a Telegram user–chat membership is created without an explicit choice +- **THEN** splitting is true + +#### Scenario: New GitHub or background membership +- **WHEN** a GitHub or background user–chat membership is created without an explicit choice +- **THEN** splitting is false + +#### Scenario: Original migration backfill +- **WHEN** the reviewed introduction migration is applied to existing memberships +- **THEN** existing WhatsApp memberships have false and other memberships have true + +#### Scenario: No membership is available during delivery +- **WHEN** a reply is delivered without a stored preference for the triggering user and chat +- **THEN** the same platform default applies without introducing a new user setting + +### Requirement: Per-chat settings API + +The existing per-chat user-settings API SHALL return `split_messages` as a boolean and accept it as a required boolean in user-chat settings saves. An authorized member SHALL be able to edit their own preference without chat-admin rights, under the existing authorization rules. + +#### Scenario: Read saved preference +- **WHEN** an authorized member fetches their settings for a chat +- **THEN** the response includes their persisted splitting preference + +#### Scenario: Non-admin member saves preference +- **WHEN** an authorized non-admin member submits a valid per-chat user-settings payload with splitting disabled +- **THEN** their preference is saved without modifying chat-wide settings or another member's preference + +#### Scenario: Invalid or missing preference +- **WHEN** a user-chat settings payload omits the required splitting preference or supplies a value rejected by the existing boolean validation rules +- **THEN** the API rejects the payload through its normal validation response + +### Requirement: Preference controls reply paragraph splitting + +The system SHALL use the triggering user's preference for normal text replies, command text replies, and burst-level error text replies in that chat. When enabled, blank-line delimiter splitting SHALL retain the current code-block and consecutive-list preservation behavior. When disabled, blank lines SHALL remain paragraph formatting inside one logical text message. Emoji-reaction replies and independently sent media or notifications SHALL retain their existing behavior. + +#### Scenario: Enabled paragraph splitting +- **WHEN** splitting is enabled and a reply contains three ordinary paragraphs separated by blank lines, each within provider limits +- **THEN** the reply is sent as three text messages in paragraph order + +#### Scenario: Disabled paragraph splitting +- **WHEN** splitting is disabled and the same reply fits within the provider limit +- **THEN** it is sent as one text message containing all three paragraphs and their blank lines + +#### Scenario: Formatting remains intact +- **WHEN** a reply contains a code block or consecutive list items separated by blank lines and fits within applicable limits +- **THEN** disabling splitting preserves the full formatted text and enabling splitting does not split inside those preserved blocks + +#### Scenario: Group reply ownership +- **WHEN** members of a group have different splitting preferences and one member triggers a reply +- **THEN** that reply uses the triggering member's preference + +#### Scenario: Command and error replies +- **WHEN** a command reply or a burst-level error reply contains blank-line-separated text +- **THEN** the triggering user's splitting preference determines optional paragraph splitting + +### Requirement: Provider size limits remain mandatory + +Both preference values SHALL respect the text-size limit of the destination provider. The system SHALL split oversized text only as needed to meet that limit, preserve content order without truncation, and retain valid code-fence formatting across required splits. + +#### Scenario: Disabled splitting at the limit +- **WHEN** splitting is disabled and the full reply is exactly at the provider's text limit +- **THEN** it is sent as one text message + +#### Scenario: Disabled splitting exceeds the limit +- **WHEN** splitting is disabled and the full reply exceeds the provider's text limit +- **THEN** it is sent in the required number of size-bounded messages without discarding text + +#### Scenario: One oversized block +- **WHEN** a single paragraph, code block, or uninterrupted span exceeds the provider limit +- **THEN** it is divided into valid size-bounded messages with its visible content preserved in order + +#### Scenario: Unicode and formatting boundaries +- **WHEN** a reply near the provider limit contains Unicode characters or formatting markup +- **THEN** each outgoing message conforms to that provider's counting/formatting rules without corrupting characters + +### Requirement: Settings checkbox + +The settings frontend SHALL show a per-user/per-chat checkbox labelled “Natural chat flow”, initialized from the backend's saved value. Saving and reloading SHALL preserve the selected value. Its helper text SHALL explain that, when enabled, the agent will send several shorter messages in quick succession instead of one long reply and that this may increase delivery costs on platforms that charge per message. + +#### Scenario: Load WhatsApp settings +- **WHEN** a user opens settings for a WhatsApp membership with its initial default +- **THEN** the checkbox is unchecked + +#### Scenario: Load Telegram settings +- **WHEN** a user opens settings for a Telegram membership with its initial default +- **THEN** the checkbox is checked + +#### Scenario: Save the checkbox +- **WHEN** an authorized member changes the checkbox and saves their settings +- **THEN** dirty-state detection and the displayed value follow the same save behavior as existing per-chat user settings diff --git a/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/tasks.md b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/tasks.md new file mode 100644 index 00000000..3d7e8600 --- /dev/null +++ b/openspec/changes/archive/2026-10-11-per-user-chat-message-splitting/tasks.md @@ -0,0 +1,97 @@ +## Execution Rules + +Implement bottom-up. Each milestone includes its owning-layer tests and an explicit manual-review stop. Never start the next milestone without the user's approval. Use existing test modules, managed production DI, shared stubs/fakes, and public subject-layer assertions; no new test files are planned. + +Keep test changes minimal: review existing cases first and extend them alongside the other settings. Add a new case only for a distinct consumer-visible failure introduced by changed logic and not already covered. Do not add flag-only CRUD/authorization tests, standard boolean validation/coercion tests, or value/platform/role matrices merely because a field was added. General requests to test do not authorize exhaustive coverage. + +Initial authorization covers task 1.1 only, followed immediately by focused tests, an offline persistence smoke, the complete offline suite, and repository-wide Ruff/spacing checks. Stop for review after that task. Migration generation remains user-controlled and must not be silently marked complete. + +Backend review commands: `pipenv run pytest -v`, `pipenv run ruff check .`, and `pipenv run python tools/check_spacing.py .`. Run the required Ruff/spacing fix commands on changed Python files first. Do not fix unrelated failures. + +## 1. Milestone 1 — Database persistence and repository tests + +- [x] 1.1 Persist `split_messages` with a non-null boolean DB column, matching minimal `ChatMembership` field, both persistence mapper directions, repository upsert, and existing DB/domain stubs. Extend existing repository save/get/upsert cases with the new field and retain existing membership-key coverage; replace incomplete mapper cases with complementary default/non-default regressions explicitly asserting all nine fields in both conversion results. Verify via public repository getters, focused tests, offline smoke, complete suite, and Ruff/spacing; stop for the initial manual review without changing services, API, reply splitting, or frontend. +- [x] 1.2 After approval, have the user run `./tools/db_generate_migration -y`; review the generated revision and add the one-time platform-aware backfill (WhatsApp false, others true). Verify `ChatMembershipDB` is imported by Alembic, verify upgrade/downgrade in an isolated environment, and leave live migration application to the user via `./tools/db_apply_migration` after explicit approval. +- [x] 1.3 Complete the database milestone's focused/full offline verification and repository-wide Ruff/spacing checks, record migration status accurately, and stop for manual review before domain behavior changes. + +Initial task 1.1 verification: 13 focused tests passed; the complete offline suite passed with 1,879 tests and 423 subtests. A production-DI offline smoke reloaded false and then true from separate database sessions. Repository-wide Ruff and spacing checks passed. No migration was generated/applied; task 1.2 awaits the user's review and generation command. Domain defaults, API, delivery, and frontend behavior remain unchanged. + +Mapper review correction: complementary cases cover all nine stub-default fields and all nine non-default overrides, with explicit assertions on the DB intermediate and domain round-trip result. The four mapper tests passed; the complete offline suite passed with 1,878 tests and 423 subtests. Offline mapper smokes showed both sets of values preserved in both directions, and repository-wide Ruff/spacing checks passed. The extra repository assertions from the superseded review attempt were reverted; no repository changes were made for this mapper correction. + +Migration task 1.2: the user generated `10fae7224448_split_reply_controls.py`. Its true column default initializes every existing membership, followed by a chat-type-filtered update setting WhatsApp memberships to false. An isolated production-DI SQLite smoke exercised the actual upgrade/downgrade across eight memberships on all four current platforms: both WhatsApp memberships became false, the six others remained true, and downgrade removed the column without removing memberships. PostgreSQL offline SQL rendering also succeeded. Live PostgreSQL upgrade/downgrade and live migration application were not run. + +Database review gate: 12 focused persistence/mapper tests passed; the complete offline suite passed with 1,878 tests and 423 subtests. Repository-wide Ruff/spacing and strict OpenSpec validation passed. Milestone 1 is complete for manual code review; the migration is not applied live, and milestone 2 has not started. + +## 2. Milestone 2 — Domain behavior and tests + +- [x] 2.1 Centralize platform splitting defaults in `integration_config.py` and resolve them through `integrations.py` at every membership creation path. Preserve stored values across synchronization and admin promotion/demotion, including after integration defaults change. Minimally extend existing creation and preference-preservation cases; assert every platform default directly in `test_integrations.py`, without repeating the policy matrix in service tests or adding flag-only cases. +- [x] 2.2 Add explicit splitting control to the existing reply mapper: enabled retains current delimiter/block behavior; disabled preserves all joined content as one logical message. Update signature callers and extend existing mapper cases only where splitting changes behavior, retaining existing formatting coverage rather than repeating every input format in both modes. Extend an existing burst success case for paragraph send order and keep the distinct burst-level error caller regression reached through a delivery failure, using real subjects and shared offline fakes. +- [x] 2.3 Run domain-focused tests, an offline mapper/defaults smoke, complete offline suite, and repository-wide Ruff/spacing checks; stop for manual review. Provider-length safety and real delivery preference resolution remain milestone 4, so do not claim end-to-end delivery is complete here. + +Domain review gate: 92 focused membership/mapper/burst-service tests and 22 subtests passed; the complete offline suite passed with 1,880 tests and 445 subtests. Required changed-file and repository-wide Ruff/spacing checks passed. A production-DI offline smoke verified inbound WhatsApp/Telegram defaults, admin-refresh defaults on all four platforms, preservation of false/true choices through promotion/demotion, and enabled/disabled mapping of paragraphs, content blocks, code, lists, empty content, and unclosed fences. Both delivery callers explicitly retain splitting enabled until milestone 4 resolves the triggering user's preference and enforces provider-size limits. Milestone 2 is complete for manual review; the settings API and frontend remain unchanged, and milestone 3 has not started. + +Domain review correction: platform defaults now belong to `SPLIT_MESSAGES_DEFAULTS` in `integration_config.py`, accessed through `integrations.resolve_default_split_messages` by both membership creation paths. A new service regression and production-DI smoke changed WhatsApp to true and Telegram to false, verified both creation paths honored those configured values, and verified subsequent policy restoration/admin changes did not overwrite stored choices. The focused membership/integrations checks passed with 128 tests and 20 subtests; the complete offline suite passed with 1,881 tests and 447 subtests. Changed-file and repository-wide Ruff/spacing checks passed. Stopped again for milestone 2 manual review; milestone 3 remains unstarted. + +Platform policy correction: only Telegram defaults to true; WhatsApp, GitHub, and background default to false for new memberships. `test_integrations.py` now directly asserts all four resolver results, and membership-service expectations match. The production-DI smoke verified every platform's creation default and preservation of explicit opposite choices through admin changes. Focused checks passed with 129 tests and 20 subtests; the complete offline suite passed with 1,882 tests and 447 subtests. Changed-file and repository-wide Ruff/spacing checks passed. Existing preferences and the original reviewed migration remain unchanged. Stopped for milestone 2 review; milestone 3 remains unstarted. + +Burst caller review correction: two regressions in `test_message_burst_service.py` verify three ordered paragraph deliveries and a failed delivery followed by a standalone error-content block with its internal blank lines preserved. `FakeTelegramBotAPI.delivery_errors_once` enables the latter while leaving the production service, mapper, and SDK real; no owned behavior is mocked or production code changed. A production-DI offline smoke exercised both caller paths successfully. Focused burst/mapper checks passed with 57 tests and 4 subtests; the complete offline suite passed with 1,884 tests and 447 subtests. Changed-file and repository-wide Ruff/spacing checks passed. Preference resolution and provider limits remain milestone 4; stopped again for milestone 2 review. + +## 3. Milestone 3 — Settings API and tests + +- [x] 3.1 Add the required boolean to existing user-chat request/response models, API response mapping, controller save logic, and relevant shared API stubs. Preserve current member/admin authorization boundaries and unrelated saved settings. +- [x] 3.2 Minimally extend existing settings-controller and response-mapper cases to assert the new field alongside other preferences, including ordinary-member saves. Use the offline API smoke for remaining integration acceptance scenarios rather than adding flag-only CRUD/authorization cases or routine boolean payload-validation tests. Update the existing API documentation with the new required field. +- [x] 3.3 Exercise the actual settings API offline, run focused API tests, complete offline suite, and repository-wide Ruff/spacing checks; stop for manual review before delivery integration. + +Milestone 3 verification: focused API tests passed (56 tests and 123 subtests); the complete offline suite passed (1,890 tests and 460 subtests); changed-file and repository-wide Ruff/spacing checks passed. Actual FastAPI GET/PATCH requests used real JWT authentication, production DI/controllers, fresh isolated SQLite request sessions, and shared offline bot fakes. The smoke verified false/true save/reload, member edits, user/chat isolation, unchanged admin authorization, chat-only saves preserving the preference, and HTTP 422 rejection of missing/invalid values without changing the saved preference. The API documentation parsed and matched the required boolean in the actual request/response schemas. Stopped for manual review before milestone 4. + +Test-scope review: removed eight redundant added cases and reduced repeated boolean/platform/role combinations across the DB, domain, and API tests. Useful assertions now extend existing cases; the explicitly requested all-field mapper round trips, platform-default policy check, and distinct burst-error caller regression remain. Routine boolean payload cases were already absent and were not restored. Focused checks passed with 231 tests and 116 subtests; the complete offline suite passed with 1,879 tests and 429 subtests. Changed-file and repository-wide Ruff/spacing checks passed. A minimal actual HTTP smoke saved and reloaded both preference values as an ordinary member through real JWT authentication and fresh isolated SQLite request sessions. No production behavior changed; stopped for review before milestone 4. + +## 4. Milestone 4 — Delivery integration, required fixes, and tests + +- [x] 4.1 Resolve the triggering author's stored preference for normal replies, commands, and burst-level error replies; use the platform default if no membership exists. Pass the resolved preference through every mapper caller without creating memberships, performing remote authorization, or changing reaction/independent notification behavior. +- [x] 4.2 Add mandatory provider-limit splitting independently of optional paragraph splitting in the existing reply-mapping path. Verify the provider's length semantics; preserve all visible text and valid code fences, prefer natural boundaries, and handle an oversized uninterrupted span without truncation. +- [x] 4.3 Minimally extend existing burst-service/mapper cases for the distinct failure risks introduced by preference resolution and provider-size limits: enabled/disabled send counts, group ownership, missing-membership defaults, command/error delivery, exact/over-limit text, large blocks, Unicode, and unchanged reactions. Reuse existing coverage; do not add mode/provider/message-kind matrices or tests for unchanged dependencies. Assert existing billable-delivery behavior per actual send without altering pricing/accounting policy. +- [x] 4.4 Exercise real SDK/service delivery through shared offline provider fakes, verify stored messages and accounting outcomes for one-vs-multiple sends, run complete offline suite and repository-wide Ruff/spacing checks, update relevant existing delivery docs if required, and stop for manual review. + +Task 4.1 verification: both reply-mapper callers now resolve the triggering author's persisted preference, falling back to the existing integration policy only when the membership is absent. Existing burst-service cases were extended; no test methods or files were added. Focused checks passed with 55 tests and 2 subtests; the complete offline suite passed with 1,879 tests and 429 subtests. Changed-file and repository-wide Ruff/spacing checks passed. Actual production-service/SDK delivery through shared offline fakes verified one WhatsApp send with splitting disabled versus three with it enabled, the triggering author's preference in a group with differing member preferences, Telegram's enabled fallback without recreating an absent membership, and a command fallback reply delivered as one message with splitting disabled. Existing burst-error and WhatsApp missing-membership cases also passed. + +Stopped for manual review after task 4.1. Mandatory provider-size splitting and the remaining milestone 4 acceptance checks are still pending; this is not the completed delivery milestone. + +Task 4.2 verification: Telegram `sendMessage` and WhatsApp Cloud API text bodies both have a 4,096-character limit. Telegram applies the limit after legacy Markdown entity parsing; WhatsApp applies it to the submitted body. The integration policy now owns those limits, and the existing reply mapper enforces them after optional paragraph splitting. Required splitting prefers paragraph, line, and word boundaries, falls back to a hard Unicode-safe boundary, and closes/reopens code fences without dropping code content. Two distinct mapper regressions cover Telegram's parsed-length/Unicode exact boundary, a natural over-limit split, and an uninterrupted oversized WhatsApp code block; no new test file or mode/provider matrix was added. The Telegram utility is named `calculate_rendered_length`, with three direct unit cases for plain Unicode text, legacy Markdown and code-block rendering, and escaped literal characters. + +Focused checks passed with 68 tests and 2 subtests; the complete offline suite passed with 1,884 tests and 429 subtests. Changed-file and repository-wide Ruff/spacing checks passed, and changed production/test files had no LSP diagnostics. Actual production-service/SDK delivery through shared offline fakes verified a 5,014-character WhatsApp code response became two bounded sends of 4,096 and 926 characters with valid fences and all 5,000 code characters stored, while a 4,098-source-character Telegram response with 4,096 parsed Unicode characters remained one stored send. + +Stopped for manual review after task 4.2. The minimal cross-path/accounting acceptance review in tasks 4.3–4.4 is still pending; this is not the completed delivery milestone. + +Milestone 4 completion: the acceptance audit reused existing mapper, burst-error, missing-membership, provider-limit, Unicode, code-fence, reaction, SDK-storage, and single-send accounting coverage. No test methods or files were added. The existing group case now includes another member with the opposite preference, and the existing three-send WhatsApp case asserts three delivery charges and the corresponding balance deduction. Actual production-service/SDK delivery through the shared offline fake verified that a disabled preference produced one send, one stored message, one delivery record, and a 0.4-credit charge, while an enabled preference produced three of each and a 1.2-credit charge. Focused cases passed after correcting the delivery-record filter to exclude unrelated model usage; the complete offline suite passed with 1,884 tests and 429 subtests. Repository-wide Ruff/spacing checks passed, and the changed test file had no LSP diagnostics. No separate delivery documentation required an update beyond the existing API documentation and this accepted specification. Stopped for manual review before milestone 5. + +## 5. Milestone 5 — Companion settings UI and visual verification + +- [x] 5.1 Use this backend change directly as the OpenSpec source while working from the sibling `agent-web` repository. Add `split_messages` to existing service types, page dirty-state/save handling, and the per-user/per-chat toggle using existing localization conventions. +- [x] 5.2 Verify the actual settings page for default values, toggle/save/reload, non-admin member access, and helper text explaining the conversational behavior and possible per-message delivery cost. Use that repository's required checks and relevant existing tests without creating new test files unless explicitly approved. +- [x] 5.3 Stop for manual UI review and confirm backend migration and coordinated rollout prerequisites. Start the final dependency-upgrade milestone only after the user's approval. + +Milestone 5 verification: `agent-web` uses this backend change directly, as selected by the user. The required `split_messages` field now participates in the existing service contract, dirty-state comparison, full user-chat save payload, and the “My experience in this chat” form, where it appears after the existing personal toggles. It reuses the existing toggle component unchanged, with no checkbox-specific undo interaction. All eleven editable translation catalogs plus generated translation keys contain the natural-chat label and per-message delivery-cost helper text. No test files were added. + +The actual frontend and backend ran together locally. A JWT generated through the backend's own Pipenv/auth implementation loaded real settings API data. The browser verified WhatsApp's false default, Telegram's true integration default, an actual false-to-true PATCH and backend reload, a hard page reload from persisted data, and restoration of the original false value through a subsequent save. A temporary real WhatsApp group membership with `is_admin = false` verified the ordinary-member surface, absence of admin controls, successful user-setting PATCH, and persisted reload; temporary records were deleted and original local data was restored. Desktop and 390-pixel responsive views showed the complete helper text without horizontal overflow, and a fresh actual page had no browser runtime or console errors. `bun run lint` and `bun run rebuild` passed. TypeScript LSP diagnostics were unavailable because no language server was configured; the TypeScript production build passed instead. + +Copy refinement after the implementation smoke: the user approved “Natural chat flow” / “Prirodan tok razgovora” and the matching concise behavior-plus-cost description. All eleven locale values were updated consistently. `bun run lint` and `bun run rebuild` passed again, and strict OpenSpec validation passed. Per the user's QA handoff, the final revised copy was validated locally only; final visual/language QA remains with the user. + +Post-QA chat-switch correction: after a successful settings save, `ChatSettingsPage` now updates both the `useChats` hook's live state and the shared cache through one `updateChats` path. Subsequent chat selection therefore loads the newly saved complete chat settings instead of stale pre-save data; `split_messages` continues to use the same controlled-value, payload, and dirty-state patterns as the other personal checkboxes. `bun run lint` and `bun run rebuild` passed. Per the user's QA handoff, no browser verification was run for this correction. + +Sticky-header refinement: `BaseSettingsPage` now exposes a generic optional `stickyTitleSubtitle`; its existing action-bar leading-content slot renders the subtitle with a muted divider and truncation. `ChatSettingsPage` supplies the selected chat name, so the base page and action bar contain no chat-specific subtitle branch. The regular page heading remains unchanged. `bun run lint` and `bun run rebuild` passed. Per the user's QA handoff, no browser verification was run. + +The migration must be applied to each deployment database before the coordinated backend/frontend release. It was available in the local backend used for this verification, but no shared or production database was changed. Stopped for manual UI review; milestone 6 dependency upgrades and Dependabot recreation remain unauthorized until approval. + +## 6. Milestone 6 — Final dependency upgrades and Dependabot refresh + +- [x] 6.1 Review `Pipfile` constraints and upgrade all backend production and development dependencies through Pipenv, using `pipenv update --dev` and updating declared constraints through Pipenv where existing pins prevent upgrades. Update `Pipfile` and `Pipfile.lock` together to the latest stable versions compatible with the project's Python/runtime requirements; report any unavoidable compatibility constraints rather than silently skipping dependencies. +- [x] 6.2 Make only compatibility fixes required by the upgraded dependencies and update affected existing tests. Preserve the reviewed message-splitting behavior, API contracts, and offline test conventions. +- [x] 6.3 Check the backend repository's open Dependabot PRs at this milestone. If any are open, post the exact command `@dependabot recreate` on each one and record the PR/comment links; if none are open, record that result. Do not merge or close PRs as part of this task. +- [x] 6.4 Verify lockfile/environment consistency, exercise actual application startup and relevant settings/reply paths offline through the shared production-DI fakes, run focused tests, the complete offline suite, and repository-wide Ruff/spacing checks. Run required fix checks on changed Python files, report upgrade results, and stop for final manual review. + +Milestone 6 dependency result: `pipenv update --dev` refreshed 52 production lock entries and 6 development lock entries, including 19 declared production packages. `Pipfile` now constrains development-only `virtualenv` to 21.14.5 because 21.14.6 requires `packaging >= 26.3` while the latest `xai-sdk` requires `packaging < 26`; `pipenv verify`, `pipenv sync --dev`, and `pip check` all pass with that compatible resolution. Other newer transitive releases remain resolver-constrained: `protobuf < 7` by `xai-sdk`, `tenacity < 9.2` by `google-genai`, `uuid-utils < 1` by `langchain-core`/`langsmith`, and `websockets < 17` by `google-genai`/`langgraph-sdk`. No application or test compatibility changes were required. + +Final verification: 225 focused tests and 116 subtests passed; the complete offline suite passed with 1,884 tests and 429 subtests. A production-DI offline smoke started the actual FastAPI lifespan, exercised `/health`, authenticated settings GET/PATCH/GET with false persistence, and delivered a two-paragraph response as one stored user-preferred message through the real burst service and fake Telegram SDK. Repository-wide Ruff and spacing checks passed. The backend repository had no open pull requests, so there were no Dependabot PRs on which to post `@dependabot recreate`. Stopped for final manual review before OpenSpec archival. + +Archive the backend and companion UI changes only after this final milestone is verified and the user approves the final review. No dependency upgrades or Dependabot commands are authorized before reaching this milestone. diff --git a/pyproject.toml b/pyproject.toml index 32a844cd..255b4938 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "the-agent" -version = "5.38.4" +version = "5.39.1" [tool.setuptools] package-dir = {"" = "src"} diff --git a/src/api/mapper/chat_settings_api_mapper.py b/src/api/mapper/chat_settings_api_mapper.py index eb317049..77dd129e 100644 --- a/src/api/mapper/chat_settings_api_mapper.py +++ b/src/api/mapper/chat_settings_api_mapper.py @@ -23,6 +23,7 @@ def domain_to_api(chat: ChatConfig, membership: ChatMembership, is_own: bool) -> user_chat_config = UserChatConfigResponse( use_about_me = membership.use_about_me, use_custom_prompt = membership.use_custom_prompt, + split_messages = membership.split_messages, max_output_tokens = membership.max_output_tokens, max_chat_history_depth = membership.max_chat_history_depth, max_iterations = membership.max_iterations, diff --git a/src/api/model/user_chat_config_payload.py b/src/api/model/user_chat_config_payload.py index 4043d517..e4e6cf50 100644 --- a/src/api/model/user_chat_config_payload.py +++ b/src/api/model/user_chat_config_payload.py @@ -4,6 +4,7 @@ class UserChatConfigPayload(BaseModel): use_about_me: bool use_custom_prompt: bool + split_messages: bool max_output_tokens: int max_chat_history_depth: int max_iterations: int diff --git a/src/api/model/user_chat_config_response.py b/src/api/model/user_chat_config_response.py index 79f06202..1167fdfd 100644 --- a/src/api/model/user_chat_config_response.py +++ b/src/api/model/user_chat_config_response.py @@ -4,6 +4,7 @@ class UserChatConfigResponse(BaseModel): use_about_me: bool use_custom_prompt: bool + split_messages: bool max_output_tokens: int max_chat_history_depth: int max_iterations: int diff --git a/src/api/settings_controller.py b/src/api/settings_controller.py index 5da7bee2..d2ce3b8d 100644 --- a/src/api/settings_controller.py +++ b/src/api/settings_controller.py @@ -236,6 +236,7 @@ def __apply_user_chat_config_changes( " Updating user_chat_config: " f"use_about_me={payload.use_about_me}, " f"use_custom_prompt={payload.use_custom_prompt}, " + f"split_messages={payload.split_messages}, " f"max_output_tokens={payload.max_output_tokens}, " f"max_chat_history_depth={payload.max_chat_history_depth}, " f"max_iterations={payload.max_iterations}", @@ -248,6 +249,7 @@ def __apply_user_chat_config_changes( is_admin = membership.is_admin, use_about_me = payload.use_about_me, use_custom_prompt = payload.use_custom_prompt, + split_messages = payload.split_messages, max_output_tokens = payload.max_output_tokens, max_chat_history_depth = payload.max_chat_history_depth, max_iterations = payload.max_iterations, diff --git a/src/db/alembic/versions/10fae7224448_split_reply_controls.py b/src/db/alembic/versions/10fae7224448_split_reply_controls.py new file mode 100644 index 00000000..8335bf5b --- /dev/null +++ b/src/db/alembic/versions/10fae7224448_split_reply_controls.py @@ -0,0 +1,36 @@ +"""split_reply_controls + +Revision ID: 10fae7224448 +Revises: 250e8786269e +Create Date: 2026-10-10 01:07:24.575033 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "10fae7224448" +down_revision: Union[str, None] = "250e8786269e" +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.add_column("chat_memberships", sa.Column("split_messages", sa.Boolean(), server_default = sa.text("true"), nullable = False)) # ruff: ignore[line-too-long] + # ### end Alembic commands ### + op.execute( + sa.text( + "UPDATE chat_memberships SET split_messages = false " + "WHERE chat_id IN " + "(SELECT chat_id FROM chat_configs WHERE chat_type = 'whatsapp')", + ), + ) + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("chat_memberships", "split_messages") + # ### end Alembic commands ### diff --git a/src/db/model/chat_membership.py b/src/db/model/chat_membership.py index 9d73a260..d60e1f96 100644 --- a/src/db/model/chat_membership.py +++ b/src/db/model/chat_membership.py @@ -13,6 +13,7 @@ class ChatMembershipDB(BaseModel): use_about_me = Column(Boolean, nullable = False, default = True, server_default = text("true")) use_custom_prompt = Column(Boolean, nullable = False, default = True, server_default = text("true")) + split_messages = Column(Boolean, nullable = False, default = True, server_default = text("true")) max_output_tokens = Column(Integer, nullable = False, default = 3500, server_default = text("3500")) max_chat_history_depth = Column(Integer, nullable = False, default = 30, server_default = text("30")) max_iterations = Column(Integer, nullable = False, default = 20, server_default = text("20")) diff --git a/src/features/chat/domain_langchain_mapper.py b/src/features/chat/domain_langchain_mapper.py index 7a31e7b3..0a449f06 100644 --- a/src/features/chat/domain_langchain_mapper.py +++ b/src/features/chat/domain_langchain_mapper.py @@ -8,7 +8,14 @@ from db.model.chat_config import ChatConfigDB from features.chat.config.chat_config import ChatConfig from features.chat.message.chat_message import ChatMessage -from features.integrations.integrations import is_the_agent, resolve_agent_user, resolve_external_handle, resolve_external_id +from features.integrations.integrations import ( + is_the_agent, + measure_text_message_length, + resolve_agent_user, + resolve_external_handle, + resolve_external_id, + resolve_text_message_limit, +) from features.prompting.prompt_library import CHAT_MESSAGE_DELIMITER from features.users.user import User from util import log @@ -80,6 +87,47 @@ def _split_preserving_blocks(content: str, delimiter: str) -> list[str]: return result +def _split_for_provider_limit(content: str, chat_type: ChatConfigDB.ChatType) -> list[str]: + limit = resolve_text_message_limit(chat_type) + if limit is None or measure_text_message_length(content, chat_type) <= limit: + return [content] + + result = [] + remaining = content + while measure_text_message_length(remaining, chat_type) > limit: + low = 1 + high = len(remaining) - 1 + split_at = 0 + while low <= high: + candidate = (low + high) // 2 + chunk = remaining[:candidate] + if fence := _unclosed_fence_marker(chunk): + chunk += f"\n{fence}" + if measure_text_message_length(chunk, chat_type) <= limit: + split_at = candidate + low = candidate + 1 + else: + high = candidate - 1 + + minimum_boundary = max(1, split_at // 2) + for delimiter in (CHAT_MESSAGE_DELIMITER, "\n", " ", "\t"): + boundary = remaining.rfind(delimiter, minimum_boundary, split_at) + if boundary >= 0: + split_at = boundary + len(delimiter) + break + + chunk = remaining[:split_at] + if fence := _unclosed_fence_marker(chunk): + result.append(f"{chunk}\n{fence}") + remaining = f"{fence}\n{remaining[split_at:]}" + else: + result.append(chunk) + remaining = remaining[split_at:] + + result.append(remaining) + return result + + class DomainLangchainMapper: def map_to_langchain( @@ -94,16 +142,19 @@ def map_to_langchain( return AIMessage(content) return HumanMessage(content) - def map_bot_message_to_storage(self, chat: ChatConfig, message: AIMessage) -> list[ChatMessage]: + def map_bot_message_to_storage(self, chat: ChatConfig, message: AIMessage, split_messages: bool) -> list[ChatMessage]: log.t(f"Mapping AI message '{message}' to storage message") result: list[ChatMessage] = [] content = CHAT_MESSAGE_DELIMITER.join(parse_ai_message_content_blocks(message, include_thinking = True)) - parts = _split_preserving_blocks(content, CHAT_MESSAGE_DELIMITER) + parts = _split_preserving_blocks(content, CHAT_MESSAGE_DELIMITER) if split_messages else [content] + bounded_parts = [] for part in parts: if not part: continue if fence := _unclosed_fence_marker(part): part += f"\n{fence}" + bounded_parts.extend(_split_for_provider_limit(part, chat.chat_type)) + for part in bounded_parts: sent_at = datetime.now() agent_user = resolve_agent_user(chat.chat_type) storage_message = ChatMessage( diff --git a/src/features/chat/membership/chat_membership.py b/src/features/chat/membership/chat_membership.py index b968ebdb..da61228a 100644 --- a/src/features/chat/membership/chat_membership.py +++ b/src/features/chat/membership/chat_membership.py @@ -9,6 +9,7 @@ class ChatMembership: is_admin: bool = False use_about_me: bool = True use_custom_prompt: bool = True + split_messages: bool = True max_output_tokens: int = 3500 max_chat_history_depth: int = 30 max_iterations: int = 20 diff --git a/src/features/chat/membership/chat_membership_mapper.py b/src/features/chat/membership/chat_membership_mapper.py index dc807de8..d6e803c8 100644 --- a/src/features/chat/membership/chat_membership_mapper.py +++ b/src/features/chat/membership/chat_membership_mapper.py @@ -12,6 +12,7 @@ def domain(db_model: ChatMembershipDB | None) -> ChatMembership | None: is_admin = db_model.is_admin, use_about_me = db_model.use_about_me, use_custom_prompt = db_model.use_custom_prompt, + split_messages = db_model.split_messages, max_output_tokens = db_model.max_output_tokens, max_chat_history_depth = db_model.max_chat_history_depth, max_iterations = db_model.max_iterations, @@ -28,6 +29,7 @@ def db(domain_model: ChatMembership | None) -> ChatMembershipDB | None: is_admin = domain_model.is_admin, use_about_me = domain_model.use_about_me, use_custom_prompt = domain_model.use_custom_prompt, + split_messages = domain_model.split_messages, max_output_tokens = domain_model.max_output_tokens, max_chat_history_depth = domain_model.max_chat_history_depth, max_iterations = domain_model.max_iterations, diff --git a/src/features/chat/membership/chat_membership_repo.py b/src/features/chat/membership/chat_membership_repo.py index 5d5bc98a..79666d56 100644 --- a/src/features/chat/membership/chat_membership_repo.py +++ b/src/features/chat/membership/chat_membership_repo.py @@ -43,6 +43,7 @@ def save(self, membership: ChatMembership) -> ChatMembership: existing.is_admin = membership.is_admin existing.use_about_me = membership.use_about_me existing.use_custom_prompt = membership.use_custom_prompt + existing.split_messages = membership.split_messages existing.max_output_tokens = membership.max_output_tokens existing.max_chat_history_depth = membership.max_chat_history_depth existing.max_iterations = membership.max_iterations diff --git a/src/features/chat/membership/chat_membership_service.py b/src/features/chat/membership/chat_membership_service.py index 8fb104ec..b6bdf41a 100644 --- a/src/features/chat/membership/chat_membership_service.py +++ b/src/features/chat/membership/chat_membership_service.py @@ -3,6 +3,7 @@ from di.di import DI from features.chat.config.chat_config import ChatConfig from features.chat.membership.chat_membership import ChatMembership +from features.integrations.integrations import resolve_default_split_messages from features.integrations.platform_bot_sdk import ChatAccess from features.users.user import User from util import log @@ -35,6 +36,7 @@ def sync(self, user: User, chat: ChatConfig) -> ChatMembership: is_admin = is_admin, use_about_me = True, use_custom_prompt = True, + split_messages = resolve_default_split_messages(chat.chat_type), ), ) if existing.is_admin != is_admin: @@ -45,6 +47,7 @@ def sync(self, user: User, chat: ChatConfig) -> ChatMembership: is_admin = is_admin, use_about_me = existing.use_about_me, use_custom_prompt = existing.use_custom_prompt, + split_messages = existing.split_messages, max_output_tokens = existing.max_output_tokens, max_chat_history_depth = existing.max_chat_history_depth, max_iterations = existing.max_iterations, @@ -72,11 +75,11 @@ def save(self, membership: ChatMembership) -> ChatMembership: return self.__di.chat_membership_repo.save(membership) def refresh_chat_memberships(self, user: User, current_admin_chats: list[ChatConfig]) -> list[ChatMembership]: - admin_chat_ids: set[UUID] = {chat.chat_id for chat in current_admin_chats} + admin_chats_by_id = {chat.chat_id: chat for chat in current_admin_chats} existing_memberships = self.__di.chat_membership_repo.get_all_for_user(user.id) existing_by_chat_id = {m.chat_id: m for m in existing_memberships} - for chat_id in admin_chat_ids: + for chat_id, chat in admin_chats_by_id.items(): existing = existing_by_chat_id.get(chat_id) if existing is None: self.__di.chat_membership_repo.save( @@ -86,6 +89,7 @@ def refresh_chat_memberships(self, user: User, current_admin_chats: list[ChatCon is_admin = True, use_about_me = True, use_custom_prompt = True, + split_messages = resolve_default_split_messages(chat.chat_type), ), ) elif not existing.is_admin: @@ -96,6 +100,7 @@ def refresh_chat_memberships(self, user: User, current_admin_chats: list[ChatCon is_admin = True, use_about_me = existing.use_about_me, use_custom_prompt = existing.use_custom_prompt, + split_messages = existing.split_messages, max_output_tokens = existing.max_output_tokens, max_chat_history_depth = existing.max_chat_history_depth, max_iterations = existing.max_iterations, @@ -103,7 +108,7 @@ def refresh_chat_memberships(self, user: User, current_admin_chats: list[ChatCon ) for chat_id, existing in existing_by_chat_id.items(): - if existing.is_admin and chat_id not in admin_chat_ids: + if existing.is_admin and chat_id not in admin_chats_by_id: self.__di.chat_membership_repo.save( ChatMembership( user_id = user.id, @@ -111,6 +116,7 @@ def refresh_chat_memberships(self, user: User, current_admin_chats: list[ChatCon is_admin = False, use_about_me = existing.use_about_me, use_custom_prompt = existing.use_custom_prompt, + split_messages = existing.split_messages, max_output_tokens = existing.max_output_tokens, max_chat_history_depth = existing.max_chat_history_depth, max_iterations = existing.max_iterations, diff --git a/src/features/chat/message_burst_service.py b/src/features/chat/message_burst_service.py index abc091bf..ec83adfd 100644 --- a/src/features/chat/message_burst_service.py +++ b/src/features/chat/message_burst_service.py @@ -18,6 +18,7 @@ format_reaction_response, is_reaction_response, resolve_agent_user, + resolve_default_split_messages, resolve_external_handle, ) from util import log @@ -160,8 +161,9 @@ def process_message( self.__react_to_message(resolved_domain_data, as_reaction) else: domain_messages = self.__di.domain_langchain_mapper.map_bot_message_to_storage( - resolved_domain_data.chat, - answer, + chat = resolved_domain_data.chat, + message = answer, + split_messages = self.__resolve_split_messages(resolved_domain_data), ) for message in domain_messages: should_notify_of_errors = True @@ -291,6 +293,12 @@ def __process_claimed_burst(self, claim: ClaimedChatMessageBurst) -> bool: log.e(f"Failed to process burst with {claim.message_count} messages", e) return False + def __resolve_split_messages(self, resolved_domain_data: IngestedChatMessage) -> bool: + membership = self.__di.chat_membership_repo.get(resolved_domain_data.author.id, resolved_domain_data.chat.chat_id) + if membership is not None: + return membership.split_messages + return resolve_default_split_messages(resolved_domain_data.chat.chat_type) + def __send_text_message( self, chat: ChatConfig, @@ -318,6 +326,7 @@ def __notify_of_errors( messages = self.__di.domain_langchain_mapper.map_bot_message_to_storage( chat = resolved_domain_data.chat, message = answer, + split_messages = self.__resolve_split_messages(resolved_domain_data), ) for message in messages: self.__send_text_message(resolved_domain_data.chat, message.text) diff --git a/src/features/chat/telegram/telegram_markdown_utils.py b/src/features/chat/telegram/telegram_markdown_utils.py index 2cf36114..f7b1e846 100644 --- a/src/features/chat/telegram/telegram_markdown_utils.py +++ b/src/features/chat/telegram/telegram_markdown_utils.py @@ -87,3 +87,59 @@ def protect_inline_code(match): text = text.replace(placeholder, f"{delimiter}{content}{delimiter}") return text + + +def calculate_rendered_length(text: str) -> int: + parsed_text = escape_markdown(text) + length = 0 + index = 0 + while index < len(parsed_text): + character = parsed_text[index] + if ( + character == "\\" + and index + 1 < len(parsed_text) + and parsed_text[index + 1] in "_*`[" + ): + length += 1 + index += 2 + continue + if character not in "_*`[": + length += 1 + index += 1 + continue + + closing_character = "]" if character == "[" else character + content_start = index + 1 + closing_marker = closing_character + if parsed_text.startswith("```", index): + content_start = index + 3 + language_end = content_start + while ( + language_end < len(parsed_text) + and not parsed_text[language_end].isspace() + and parsed_text[language_end] != "`" + ): + language_end += 1 + if ( + language_end > content_start + and language_end < len(parsed_text) + and parsed_text[language_end] != "`" + ): + content_start = language_end + if parsed_text[content_start:content_start + 2] in ("\n\r", "\r\n"): + content_start += 2 + elif content_start < len(parsed_text) and parsed_text[content_start] in "\n\r": + content_start += 1 + closing_marker = "```" + + content_end = parsed_text.find(closing_marker, content_start) + if content_end < 0: + length += 1 + index += 1 + continue + length += len(parsed_text[content_start:content_end]) + index = content_end + len(closing_marker) + if character == "[" and index < len(parsed_text) and parsed_text[index] == "(": + closing_parenthesis = parsed_text.find(")", index + 1) + index = len(parsed_text) if closing_parenthesis < 0 else closing_parenthesis + 1 + return length diff --git a/src/features/integrations/integration_config.py b/src/features/integrations/integration_config.py index eb6d9016..d0dd8a9b 100644 --- a/src/features/integrations/integration_config.py +++ b/src/features/integrations/integration_config.py @@ -2,10 +2,23 @@ from pydantic import SecretStr +from db.model.chat_config import ChatConfigDB from db.model.user import UserDB from features.users.user import User from util.config import config +SPLIT_MESSAGES_DEFAULTS: dict[ChatConfigDB.ChatType, bool] = { + ChatConfigDB.ChatType.telegram: True, + ChatConfigDB.ChatType.whatsapp: False, + ChatConfigDB.ChatType.github: False, + ChatConfigDB.ChatType.background: False, +} +TEXT_MESSAGE_LIMITS: dict[ChatConfigDB.ChatType, int] = { + ChatConfigDB.ChatType.telegram: 4096, + ChatConfigDB.ChatType.whatsapp: 4096, +} + + # === Core Agent (chat and core operations) === THE_AGENT = User( diff --git a/src/features/integrations/integrations.py b/src/features/integrations/integrations.py index 89f90ec4..a778e688 100644 --- a/src/features/integrations/integrations.py +++ b/src/features/integrations/integrations.py @@ -7,13 +7,16 @@ from di.di import DI from features.accounting.usage.participant_details import ParticipantInfo from features.chat.config.chat_config import ChatConfig +from features.chat.telegram.telegram_markdown_utils import calculate_rendered_length from features.external_tools.external_tool import ExternalTool from features.external_tools.external_tool_library import TELEGRAM_MESSAGE_DELIVERY, WHATSAPP_MESSAGE_DELIVERY from features.integrations.integration_config import ( BACKGROUND_AGENT, + SPLIT_MESSAGES_DEFAULTS, TELEGRAM_REACTION_INITIAL_DELAY_S, TELEGRAM_REACTION_INTERVAL_S, TELEGRAM_REACTIONS, + TEXT_MESSAGE_LIMITS, THE_AGENT, WHATSAPP_REACTION_INITIAL_DELAY_S, WHATSAPP_REACTION_INTERVAL_S, @@ -42,6 +45,20 @@ def delivery_tool_for(platform: ChatConfigDB.ChatType) -> ExternalTool: }[platform] +def resolve_default_split_messages(chat_type: ChatConfigDB.ChatType) -> bool: + return SPLIT_MESSAGES_DEFAULTS[chat_type] + + +def resolve_text_message_limit(chat_type: ChatConfigDB.ChatType) -> int | None: + return TEXT_MESSAGE_LIMITS.get(chat_type) + + +def measure_text_message_length(text: str, chat_type: ChatConfigDB.ChatType) -> int: + if chat_type == ChatConfigDB.ChatType.telegram: + return calculate_rendered_length(text) + return len(text) + + def resolve_external_id(user: User, chat_type: ChatConfigDB.ChatType) -> str | None: match chat_type: case ChatConfigDB.ChatType.telegram: diff --git a/test/api/mapper/test_chat_settings_api_mapper.py b/test/api/mapper/test_chat_settings_api_mapper.py index 23081568..63ba770b 100644 --- a/test/api/mapper/test_chat_settings_api_mapper.py +++ b/test/api/mapper/test_chat_settings_api_mapper.py @@ -43,6 +43,7 @@ def test_domain_to_api_conversion(self): # user_chat_config block self.assertTrue(api.user_chat_config.use_about_me) self.assertFalse(api.user_chat_config.use_custom_prompt) + self.assertTrue(api.user_chat_config.split_messages) self.assertEqual(api.user_chat_config.max_output_tokens, 3500) self.assertEqual(api.user_chat_config.max_chat_history_depth, 30) self.assertEqual(api.user_chat_config.max_iterations, 20) @@ -62,6 +63,7 @@ def test_non_admin_member_mapping(self): chat_id = chat.chat_id, use_about_me = False, use_custom_prompt = False, + split_messages = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, @@ -72,6 +74,7 @@ def test_non_admin_member_mapping(self): self.assertFalse(api.chat_config.is_own) self.assertFalse(api.user_chat_config.use_about_me) self.assertFalse(api.user_chat_config.use_custom_prompt) + self.assertFalse(api.user_chat_config.split_messages) self.assertEqual(api.user_chat_config.max_output_tokens, 500) self.assertEqual(api.user_chat_config.max_chat_history_depth, 5) self.assertEqual(api.user_chat_config.max_iterations, 3) diff --git a/test/api/test_settings_controller.py b/test/api/test_settings_controller.py index d9af3427..ecb1e539 100644 --- a/test/api/test_settings_controller.py +++ b/test/api/test_settings_controller.py @@ -428,21 +428,30 @@ def test_save_chat_settings_success_chat_config(self): self.assertFalse(result.is_private) def test_save_chat_settings_success_user_chat_config(self): + self.bot.members[(self.chat.external_id, str(self.user.telegram_user_id))] = stubs.external.telegram_chat_member() payload = stubs.api.chat_settings_payload( chat_config = None, user_chat_config = stubs.api.user_chat_config_payload( - use_about_me = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, + use_about_me = False, + split_messages = False, + max_output_tokens = 500, + max_chat_history_depth = 5, + max_iterations = 3, ), ) self.controller.save_chat_settings(self.chat.chat_id.hex, payload) - result = self.controller.fetch_chat_settings(self.chat.chat_id.hex).user_chat_config + settings = self.controller.fetch_chat_settings(self.chat.chat_id.hex) + result = settings.user_chat_config + self.assertFalse(settings.chat_config.is_admin) self.assertFalse(result.use_about_me) self.assertTrue(result.use_custom_prompt) + self.assertFalse(result.split_messages) self.assertEqual(result.max_output_tokens, 500) self.assertEqual(result.max_chat_history_depth, 5) self.assertEqual(result.max_iterations, 3) + self.assertEqual(self.di.chat_config_repo.get(self.chat.chat_id), self.chat) def test_save_chat_settings_failure_non_admin_chat_config_rejected(self): self.bot.members[(self.chat.external_id, str(self.user.telegram_user_id))] = stubs.external.telegram_chat_member() @@ -473,6 +482,7 @@ def test_fetch_all_chat_settings_success(self): self.assertTrue(result[0].chat_config.is_admin) self.assertEqual(result[0].chat_config.platform, "telegram") self.assertTrue(result[0].user_chat_config.use_about_me) + self.assertTrue(result[0].user_chat_config.split_messages) def test_fetch_all_chat_settings_sort_order(self): private_chat = self.di.chat_config_repo.save(stubs.domain.chat_config( @@ -504,6 +514,7 @@ def test_fetch_chat_settings_success(self): self.assertEqual(result.chat_config.chat_id, self.chat.chat_id.hex) self.assertTrue(result.chat_config.is_admin) self.assertEqual(result.user_chat_config.max_output_tokens, 3500) + self.assertTrue(result.user_chat_config.split_messages) def test_create_settings_link_with_sponsorship(self): self.di.sponsorship_repo.save(stubs.domain.sponsorship(sponsor_id = self.agent.id, receiver_id = self.user.id)) diff --git a/test/fakes/fake_telegram_bot_api.py b/test/fakes/fake_telegram_bot_api.py index 37fa36f7..775b7fb5 100644 --- a/test/fakes/fake_telegram_bot_api.py +++ b/test/fakes/fake_telegram_bot_api.py @@ -18,6 +18,7 @@ class FakeTelegramBotAPI: statuses: dict[str, str] reactions: dict[tuple[str, str], str | None] delivery_errors: dict[str, Exception] + delivery_errors_once: dict[str, Exception] photo_error: Exception | None video_error: Exception | None @@ -28,6 +29,7 @@ def __init__(self): self.statuses = {} self.reactions = {} self.delivery_errors = {} + self.delivery_errors_once = {} self.photo_error = None self.video_error = None @@ -136,7 +138,10 @@ def download_file(self, file_id: str) -> bytes | None: return content def _send(self, message: dict[str, Any]) -> dict: - error = self.delivery_errors.get(str(message["chat_id"])) + chat_id = str(message["chat_id"]) + error = self.delivery_errors_once.pop(chat_id, None) + if error is None: + error = self.delivery_errors.get(chat_id) if error is not None: raise error message_id = str(len(self._messages) + 1) diff --git a/test/features/chat/membership/test_chat_membership_mapper.py b/test/features/chat/membership/test_chat_membership_mapper.py index 24f49ea3..e82a1943 100644 --- a/test/features/chat/membership/test_chat_membership_mapper.py +++ b/test/features/chat/membership/test_chat_membership_mapper.py @@ -1,4 +1,5 @@ import unittest +from uuid import UUID import stubs @@ -13,37 +14,70 @@ def test_domain_returns_none_for_none_input(self): def test_db_returns_none_for_none_input(self): self.assertIsNone(db(None)) - def test_domain_maps_all_fields(self): - db_model = stubs.db.chat_membership_db() - - result = domain(db_model) + def test_roundtrip_preserves_all_default_values(self): + original = stubs.domain.chat_membership() - self.assertIsNotNone(result) - self.assertEqual(result.user_id, db_model.user_id) - self.assertEqual(result.chat_id, db_model.chat_id) - self.assertEqual(result.is_admin, db_model.is_admin) - self.assertEqual(result.use_about_me, db_model.use_about_me) - self.assertEqual(result.use_custom_prompt, db_model.use_custom_prompt) + db_model = db(original) - def test_db_maps_all_fields(self): - domain_model = stubs.domain.chat_membership() + self.assertIsNotNone(db_model) + self.assertEqual(db_model.user_id, original.user_id) + self.assertEqual(db_model.chat_id, original.chat_id) + self.assertEqual(db_model.is_admin, original.is_admin) + self.assertEqual(db_model.use_about_me, original.use_about_me) + self.assertEqual(db_model.use_custom_prompt, original.use_custom_prompt) + self.assertEqual(db_model.split_messages, original.split_messages) + self.assertEqual(db_model.max_output_tokens, original.max_output_tokens) + self.assertEqual(db_model.max_chat_history_depth, original.max_chat_history_depth) + self.assertEqual(db_model.max_iterations, original.max_iterations) - result = db(domain_model) + result = domain(db_model) self.assertIsNotNone(result) - self.assertEqual(result.user_id, domain_model.user_id) - self.assertEqual(result.chat_id, domain_model.chat_id) - self.assertEqual(result.is_admin, domain_model.is_admin) - self.assertEqual(result.use_about_me, domain_model.use_about_me) - self.assertEqual(result.use_custom_prompt, domain_model.use_custom_prompt) - - def test_roundtrip_domain_to_db_to_domain(self): - original = stubs.domain.chat_membership() - - result = domain(db(original)) - self.assertEqual(result.user_id, original.user_id) self.assertEqual(result.chat_id, original.chat_id) self.assertEqual(result.is_admin, original.is_admin) self.assertEqual(result.use_about_me, original.use_about_me) self.assertEqual(result.use_custom_prompt, original.use_custom_prompt) + self.assertEqual(result.split_messages, original.split_messages) + self.assertEqual(result.max_output_tokens, original.max_output_tokens) + self.assertEqual(result.max_chat_history_depth, original.max_chat_history_depth) + self.assertEqual(result.max_iterations, original.max_iterations) + + def test_roundtrip_preserves_all_non_default_values(self): + original = stubs.domain.chat_membership( + user_id = UUID(int = 1), + chat_id = UUID(int = 2), + is_admin = True, + use_about_me = False, + use_custom_prompt = False, + split_messages = False, + max_output_tokens = 500, + max_chat_history_depth = 5, + max_iterations = 3, + ) + + db_model = db(original) + + self.assertIsNotNone(db_model) + self.assertEqual(db_model.user_id, UUID(int = 1)) + self.assertEqual(db_model.chat_id, UUID(int = 2)) + self.assertTrue(db_model.is_admin) + self.assertFalse(db_model.use_about_me) + self.assertFalse(db_model.use_custom_prompt) + self.assertFalse(db_model.split_messages) + self.assertEqual(db_model.max_output_tokens, 500) + self.assertEqual(db_model.max_chat_history_depth, 5) + self.assertEqual(db_model.max_iterations, 3) + + result = domain(db_model) + + self.assertIsNotNone(result) + self.assertEqual(result.user_id, UUID(int = 1)) + self.assertEqual(result.chat_id, UUID(int = 2)) + self.assertTrue(result.is_admin) + self.assertFalse(result.use_about_me) + self.assertFalse(result.use_custom_prompt) + self.assertFalse(result.split_messages) + self.assertEqual(result.max_output_tokens, 500) + self.assertEqual(result.max_chat_history_depth, 5) + self.assertEqual(result.max_iterations, 3) diff --git a/test/features/chat/membership/test_chat_membership_repo.py b/test/features/chat/membership/test_chat_membership_repo.py index 9c8b2a1a..bd8c5988 100644 --- a/test/features/chat/membership/test_chat_membership_repo.py +++ b/test/features/chat/membership/test_chat_membership_repo.py @@ -41,6 +41,7 @@ def test_save_creates_new_membership(self): membership = stubs.domain.chat_membership( user_id = self.user.id, chat_id = self.chat.chat_id, + split_messages = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, @@ -53,6 +54,7 @@ def test_save_creates_new_membership(self): self.assertFalse(result.is_admin) self.assertTrue(result.use_about_me) self.assertTrue(result.use_custom_prompt) + self.assertFalse(result.split_messages) self.assertEqual(result.max_output_tokens, 500) self.assertEqual(result.max_chat_history_depth, 5) self.assertEqual(result.max_iterations, 3) @@ -63,6 +65,7 @@ def test_get_returns_saved_membership(self): chat_id = self.chat.chat_id, is_admin = True, use_about_me = False, + split_messages = True, max_output_tokens = 1000, max_chat_history_depth = 10, max_iterations = 7, @@ -75,6 +78,7 @@ def test_get_returns_saved_membership(self): self.assertTrue(result.is_admin) self.assertFalse(result.use_about_me) self.assertTrue(result.use_custom_prompt) + self.assertTrue(result.split_messages) self.assertEqual(result.max_output_tokens, 1000) self.assertEqual(result.max_chat_history_depth, 10) self.assertEqual(result.max_iterations, 7) @@ -83,6 +87,7 @@ def test_save_upserts_existing_membership(self): original = stubs.domain.chat_membership( user_id = self.user.id, chat_id = self.chat.chat_id, + split_messages = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, @@ -95,6 +100,7 @@ def test_save_upserts_existing_membership(self): is_admin = True, use_about_me = False, use_custom_prompt = False, + split_messages = True, max_output_tokens = 8000, max_chat_history_depth = 50, max_iterations = 10, @@ -104,12 +110,14 @@ def test_save_upserts_existing_membership(self): self.assertTrue(result.is_admin) self.assertFalse(result.use_about_me) self.assertFalse(result.use_custom_prompt) + self.assertTrue(result.split_messages) self.assertEqual(result.max_output_tokens, 8000) self.assertEqual(result.max_chat_history_depth, 50) self.assertEqual(result.max_iterations, 10) fetched = self.repo.get(self.user.id, self.chat.chat_id) self.assertTrue(fetched.is_admin) self.assertEqual(fetched.max_output_tokens, 8000) + self.assertTrue(fetched.split_messages) def test_get_all_for_user_returns_memberships(self): second_chat = self.di.chat_config_repo.save( diff --git a/test/features/chat/membership/test_chat_membership_service.py b/test/features/chat/membership/test_chat_membership_service.py index 9e7d15e5..9e15fcb7 100644 --- a/test/features/chat/membership/test_chat_membership_service.py +++ b/test/features/chat/membership/test_chat_membership_service.py @@ -1,12 +1,12 @@ import unittest -from dataclasses import replace from typing import cast -from uuid import UUID +from uuid import UUID, uuid4 import stubs from fakes.fake_telegram_bot_api import FakeTelegramBotAPI from util.di_utils import di_for_tests +from db.model.chat_config import ChatConfigDB from di.di import DI from features.chat.config.chat_config import ChatConfig from features.chat.membership.chat_membership_repo import ChatMembershipRepository @@ -144,6 +144,7 @@ def test_sync_returns_existing_unchanged_when_admin_matches(self): chat_id = self.chat.chat_id, use_about_me = False, use_custom_prompt = False, + split_messages = False, ), ) self.bot.members[self.member_key] = stubs.external.telegram_chat_member() @@ -153,6 +154,7 @@ def test_sync_returns_existing_unchanged_when_admin_matches(self): self.assertEqual(result.user_id, existing.user_id) self.assertFalse(result.is_admin) self.assertFalse(result.use_about_me) + self.assertFalse(result.split_messages) self.assertEqual(self.repo.get(self.user.id, self.chat.chat_id), existing) def test_sync_refreshes_admin_status_on_existing(self): @@ -162,6 +164,7 @@ def test_sync_refreshes_admin_status_on_existing(self): chat_id = self.chat.chat_id, use_about_me = False, use_custom_prompt = False, + split_messages = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, @@ -174,6 +177,7 @@ def test_sync_refreshes_admin_status_on_existing(self): self.assertTrue(result.is_admin) self.assertFalse(result.use_about_me) self.assertFalse(result.use_custom_prompt) + self.assertFalse(result.split_messages) self.assertEqual(result.max_output_tokens, 500) self.assertEqual(result.max_chat_history_depth, 5) self.assertEqual(result.max_iterations, 3) @@ -189,6 +193,7 @@ def test_sync_creates_with_admin_access(self): self.assertTrue(result.is_admin) self.assertTrue(result.use_about_me) self.assertTrue(result.use_custom_prompt) + self.assertTrue(result.split_messages) self.assertEqual(self.repo.get(self.user.id, self.chat.chat_id), result) def test_sync_creates_with_member_access(self): @@ -199,6 +204,7 @@ def test_sync_creates_with_member_access(self): self.assertFalse(result.is_admin) self.assertTrue(result.use_about_me) self.assertTrue(result.use_custom_prompt) + self.assertTrue(result.split_messages) stored = self.service.get(self.user.id, self.chat.chat_id) self.assertIsNotNone(stored) @@ -209,6 +215,7 @@ def test_ensure_for_inbound_returns_cached_membership_without_platform_lookup(se chat_id = self.chat.chat_id, is_admin = True, use_about_me = False, + split_messages = False, ), ) self.bot.members.clear() @@ -219,6 +226,7 @@ def test_ensure_for_inbound_returns_cached_membership_without_platform_lookup(se self.assertEqual(result.chat_id, existing.chat_id) self.assertTrue(result.is_admin) self.assertFalse(result.use_about_me) + self.assertFalse(result.split_messages) self.assertEqual(self.repo.get(self.user.id, self.chat.chat_id), existing) def test_ensure_for_inbound_creates_missing_membership(self): @@ -251,15 +259,26 @@ def test_ensure_for_inbound_rejects_membership_when_platform_lookup_fails(self): self.assertIsNone(self.repo.get(self.user.id, self.chat.chat_id)) def test_sync_creates_with_owner_access(self): - self.chat = replace(self.chat, is_private = True, external_id = str(self.user.telegram_user_id)) + user = stubs.domain.user(whatsapp_user_id = "15551234567") self.bot.members.clear() - result = self.service.sync(self.user, self.chat) + for chat_type, external_id, expected_split_messages in ( + (ChatConfigDB.ChatType.telegram, user.telegram_chat_id, True), + (ChatConfigDB.ChatType.whatsapp, user.whatsapp_user_id, False), + ): + with self.subTest(chat_type = chat_type): + chat = stubs.domain.chat_config( + chat_id = uuid4(), + chat_type = chat_type, + external_id = external_id, + is_private = True, + ) - self.assertTrue(result.is_admin) - self.assertEqual(self.repo.get(self.user.id, self.chat.chat_id), result) - stored = self.service.get(self.user.id, self.chat.chat_id) - self.assertIsNotNone(stored) + result = self.service.sync(user, chat) + + self.assertTrue(result.is_admin) + self.assertEqual(result.split_messages, expected_split_messages) + self.assertEqual(self.repo.get(user.id, chat.chat_id), result) def test_sync_rejects_non_participant(self): self.bot.members[self.member_key] = stubs.external.telegram_chat_member_left() @@ -301,6 +320,7 @@ def test_refresh_chat_memberships_preserves_preferences_on_promote(self): chat_id = self.chat.chat_id, use_about_me = False, use_custom_prompt = False, + split_messages = False, max_output_tokens = 500, max_chat_history_depth = 5, max_iterations = 3, @@ -312,6 +332,7 @@ def test_refresh_chat_memberships_preserves_preferences_on_promote(self): self.assertTrue(result[0].is_admin) self.assertFalse(result[0].use_about_me) self.assertFalse(result[0].use_custom_prompt) + self.assertFalse(result[0].split_messages) self.assertEqual(result[0].max_output_tokens, 500) self.assertEqual(result[0].max_chat_history_depth, 5) self.assertEqual(result[0].max_iterations, 3) @@ -322,6 +343,7 @@ def test_refresh_chat_memberships_demotes_stale_admin(self): user_id = self.user.id, chat_id = self.chat.chat_id, is_admin = True, + split_messages = False, ), ) @@ -331,6 +353,7 @@ def test_refresh_chat_memberships_demotes_stale_admin(self): self.assertFalse(result[0].is_admin) self.assertTrue(result[0].use_about_me) self.assertTrue(result[0].use_custom_prompt) + self.assertFalse(result[0].split_messages) def test_refresh_chat_memberships_preserves_already_correct_admin_membership(self): self.di.chat_membership_repo.save( @@ -338,6 +361,7 @@ def test_refresh_chat_memberships_preserves_already_correct_admin_membership(sel user_id = self.user.id, chat_id = self.chat.chat_id, is_admin = True, + split_messages = False, ), ) @@ -345,14 +369,23 @@ def test_refresh_chat_memberships_preserves_already_correct_admin_membership(sel stored = self.service.get(self.user.id, self.chat.chat_id) self.assertTrue(stored.is_admin) + self.assertFalse(stored.split_messages) def test_refresh_chat_memberships_creates_missing_admin_row_with_defaults(self): - result = self.service.refresh_chat_memberships(self.user, [self.chat]) - - self.assertEqual(len(result), 1) - self.assertTrue(result[0].is_admin) - self.assertTrue(result[0].use_about_me) - self.assertTrue(result[0].use_custom_prompt) + for chat_type, expected_split_messages in ( + (ChatConfigDB.ChatType.whatsapp, False), + (ChatConfigDB.ChatType.telegram, True), + ): + with self.subTest(chat_type = chat_type): + chat = stubs.domain.chat_config(chat_id = uuid4(), chat_type = chat_type) + + result = self.service.refresh_chat_memberships(self.user, [chat]) + + membership = next(m for m in result if m.chat_id == chat.chat_id) + self.assertTrue(membership.is_admin) + self.assertTrue(membership.use_about_me) + self.assertTrue(membership.use_custom_prompt) + self.assertEqual(membership.split_messages, expected_split_messages) def test_refresh_chat_memberships_handles_multiple_chats(self): second_chat = stubs.domain.chat_config(chat_id = UUID("33333333-3333-4333-8333-c33333333333")) diff --git a/test/features/chat/telegram/test_telegram_markdown_utils.py b/test/features/chat/telegram/test_telegram_markdown_utils.py index 5eff0ac4..55f24a73 100644 --- a/test/features/chat/telegram/test_telegram_markdown_utils.py +++ b/test/features/chat/telegram/test_telegram_markdown_utils.py @@ -1,10 +1,21 @@ import unittest -from features.chat.telegram.telegram_markdown_utils import escape_markdown +from features.chat.telegram.telegram_markdown_utils import calculate_rendered_length, escape_markdown class TelegramMarkdownUtilsTest(unittest.TestCase): + def test_calculate_rendered_length_plain_text_and_unicode(self): + self.assertEqual(calculate_rendered_length("Hello 😀"), 7) + + def test_calculate_rendered_length_excludes_legacy_markdown(self): + self.assertEqual(calculate_rendered_length("Use *bold*, _italic_, and `code`."), 27) + self.assertEqual(calculate_rendered_length("```python\nprint('hi')\n```"), 12) + + def test_calculate_rendered_length_counts_escaped_literals(self): + text = "snake_case [value]" + self.assertEqual(calculate_rendered_length(text), len(text)) + def test_escape_markdown_preserves_bold(self): # Telegram uses *bold* (single asterisk, not double!) self.assertEqual(escape_markdown("This is *bold* text"), "This is *bold* text") diff --git a/test/features/chat/test_domain_langchain_mapper.py b/test/features/chat/test_domain_langchain_mapper.py index 02ba2d2b..b5caf8ff 100644 --- a/test/features/chat/test_domain_langchain_mapper.py +++ b/test/features/chat/test_domain_langchain_mapper.py @@ -2,21 +2,25 @@ from uuid import UUID import stubs +from util.di_utils import di_for_tests from db.model.chat_config import ChatConfigDB +from di.di import DI from features.chat.domain_langchain_mapper import DomainLangchainMapper, _split_preserving_blocks -from features.integrations.integrations import resolve_agent_user +from features.integrations.integrations import measure_text_message_length, resolve_agent_user, resolve_text_message_limit from features.prompting.prompt_library import CHAT_MESSAGE_DELIMITER from features.users.user import User class DomainLangchainMapperTest(unittest.TestCase): + di: DI mapper: DomainLangchainMapper agent_user: User def setUp(self): - self.mapper = DomainLangchainMapper() + self.di = self.enterContext(di_for_tests()) + self.mapper = self.di.domain_langchain_mapper self.agent_user = resolve_agent_user(ChatConfigDB.ChatType.telegram) def test_map_to_langchain_with_author(self): @@ -64,40 +68,83 @@ def test_map_bot_message_to_storage_single_message(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "Test message") self.assertEqual(result[0].chat_id, chat.chat_id) self.assertEqual(result[0].author_id, self.agent_user.id) def test_map_bot_message_to_storage_multiple_messages(self): - message = stubs.external.ai_message(content = f"Message 1{CHAT_MESSAGE_DELIMITER}Message 2{CHAT_MESSAGE_DELIMITER}Message 3") # ruff: ignore[line-too-long] + content = f"Message 1{CHAT_MESSAGE_DELIMITER}Message 2{CHAT_MESSAGE_DELIMITER}Message 3" + message = stubs.external.ai_message(content = content) chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) - self.assertEqual(len(result), 3) - self.assertEqual(result[0].text, "Message 1") - self.assertEqual(result[1].text, "Message 2") - self.assertEqual(result[2].text, "Message 3") - for message in result: - self.assertEqual(message.chat_id, chat.chat_id) - self.assertEqual(message.author_id, self.agent_user.id) + for split_messages, expected_texts in ( + (True, ["Message 1", "Message 2", "Message 3"]), + (False, [content]), + ): + with self.subTest(split_messages = split_messages): + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = split_messages) + + self.assertEqual([item.text for item in result], expected_texts) + for item in result: + self.assertEqual(item.chat_id, chat.chat_id) + self.assertEqual(item.author_id, self.agent_user.id) + + def test_map_bot_message_to_storage_respects_provider_limit(self): + chat = stubs.domain.chat_config(chat_type = ChatConfigDB.ChatType.telegram) + limit = resolve_text_message_limit(chat.chat_type) + exact_content = f"*{'😀' * limit}*" + + exact = self.mapper.map_bot_message_to_storage( + chat, + stubs.external.ai_message(content = exact_content), + split_messages = False, + ) + oversized_content = f"{'a' * 3000}{CHAT_MESSAGE_DELIMITER}{'b' * 2000}" + oversized = self.mapper.map_bot_message_to_storage( + chat, + stubs.external.ai_message(content = oversized_content), + split_messages = False, + ) + + self.assertEqual([message.text for message in exact], [exact_content]) + self.assertEqual([message.text for message in oversized], [ + f"{'a' * 3000}{CHAT_MESSAGE_DELIMITER}", + "b" * 2000, + ]) + self.assertTrue(all(measure_text_message_length(message.text, chat.chat_type) <= limit for message in oversized)) + + def test_map_bot_message_to_storage_splits_oversized_code_block(self): + chat = stubs.domain.chat_config(chat_type = ChatConfigDB.ChatType.whatsapp) + content = f"```python\n{'x' * 5000}\n```" + + result = self.mapper.map_bot_message_to_storage(chat, stubs.external.ai_message(content = content), split_messages = False) # ruff: ignore[line-too-long] + + self.assertEqual(len(result), 2) + self.assertTrue( + all(message.text.startswith("```") and message.text.endswith("```") for message in result), + ) + self.assertTrue( + all(measure_text_message_length(message.text, chat.chat_type) <= resolve_text_message_limit(chat.chat_type) for message in result), # ruff: ignore[line-too-long] + ) + self.assertEqual(sum(message.text.count("x") for message in result), 5000) def test_map_bot_message_to_storage_empty_message(self): message = stubs.external.ai_message(content = "") chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) - self.assertEqual(len(result), 0) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = False) + self.assertEqual(result, []) def test_map_bot_message_to_storage_list_of_strings(self): message = stubs.external.ai_message(content = ["Message 1", "Message 2", "Message 3"]) chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 3) self.assertEqual(result[0].text, "Message 1") self.assertEqual(result[1].text, "Message 2") @@ -111,7 +158,7 @@ def test_map_bot_message_to_storage_list_of_dicts(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 2) self.assertEqual(result[0].text, "name: Mike\ncity: Valencia") self.assertEqual(result[1].text, "name: Dirk") @@ -125,7 +172,7 @@ def test_map_bot_message_to_storage_content_block_format(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "Hello, world!") self.assertEqual(result[0].chat_id, chat.chat_id) @@ -142,7 +189,7 @@ def test_map_bot_message_to_storage_content_block_format_multiple(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 2) self.assertEqual(result[0].text, "First message") self.assertEqual(result[1].text, "Second message") @@ -155,8 +202,8 @@ def test_map_bot_message_to_storage_message_id_uniqueness(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result1 = self.mapper.map_bot_message_to_storage(chat, message) - result2 = self.mapper.map_bot_message_to_storage(chat, message) + result1 = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) + result2 = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertNotEqual(result1[0].message_id, result2[0].message_id) def test_map_bot_message_to_storage_preserves_code_block(self): @@ -165,7 +212,7 @@ def test_map_bot_message_to_storage_preserves_code_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 3) self.assertEqual(result[0].text, "Here's code:") self.assertEqual(result[1].text, f"```python\nx = 1{CHAT_MESSAGE_DELIMITER}y = 2\n```") @@ -178,7 +225,7 @@ def test_map_bot_message_to_storage_preserves_list(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 3) self.assertEqual(result[0].text, "Steps:") self.assertEqual(result[1].text, f"- First{CHAT_MESSAGE_DELIMITER}- Second{CHAT_MESSAGE_DELIMITER}- Third") @@ -190,10 +237,12 @@ def test_map_bot_message_to_storage_closes_unclosed_code_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 2) self.assertEqual(result[0].text, "Here:") self.assertEqual(result[1].text, "```python\nprint('hi')\n```") + unsplit = self.mapper.map_bot_message_to_storage(chat, message, split_messages = False) + self.assertEqual([item.text for item in unsplit], ["Here:\n\n```python\nprint('hi')\n```"]) def test_map_bot_message_to_storage_formats_thinking_block(self): message = stubs.external.ai_message( @@ -205,10 +254,12 @@ def test_map_bot_message_to_storage_formats_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 2) self.assertEqual(result[0].text, "💭\n> some reasoning") self.assertEqual(result[1].text, "Hello!") + unsplit = self.mapper.map_bot_message_to_storage(chat, message, split_messages = False) + self.assertEqual([item.text for item in unsplit], ["💭\n> some reasoning\n\nHello!"]) def test_map_bot_message_to_storage_formats_multiline_thinking_block(self): message = stubs.external.ai_message( @@ -220,7 +271,7 @@ def test_map_bot_message_to_storage_formats_multiline_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 2) self.assertEqual(result[0].text, "💭\n> line one\n> line two\n> line three") self.assertEqual(result[1].text, "Answer.") @@ -235,7 +286,7 @@ def test_map_bot_message_to_storage_skips_empty_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "Hello!") @@ -248,7 +299,7 @@ def test_map_bot_message_to_storage_only_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "💭\n> some reasoning") @@ -261,7 +312,7 @@ def test_map_bot_message_to_storage_only_empty_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = False) self.assertEqual(len(result), 0) def test_map_bot_message_to_storage_skips_redacted_thinking_block(self): @@ -274,7 +325,7 @@ def test_map_bot_message_to_storage_skips_redacted_thinking_block(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = True) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "Hello!") @@ -284,7 +335,7 @@ def test_map_bot_message_to_storage_closes_unclosed_tilde_fence(self): chat = stubs.domain.chat_config( chat_id = UUID(int = 3), ) - result = self.mapper.map_bot_message_to_storage(chat, message) + result = self.mapper.map_bot_message_to_storage(chat, message, split_messages = False) self.assertEqual(len(result), 1) self.assertEqual(result[0].text, "~~~\nsome code\n~~~") diff --git a/test/features/chat/test_message_burst_service.py b/test/features/chat/test_message_burst_service.py index 9e67c766..f60c9684 100644 --- a/test/features/chat/test_message_burst_service.py +++ b/test/features/chat/test_message_burst_service.py @@ -4,6 +4,7 @@ from typing import cast from unittest import TestCase from unittest.mock import patch +from uuid import uuid4 from fakes.fake_chat_model import FakeChatModel from fakes.fake_telegram_bot_api import FakeTelegramBotAPI @@ -17,6 +18,7 @@ from di.di import DI from features.chat.ingested_chat_message import IngestedChatMessage from features.chat.message_burst_service import MessageBurstService +from features.external_tools.external_tool import ToolType from features.integrations.integrations import resolve_agent_user, resolve_external_handle from util.config import config @@ -101,6 +103,23 @@ def test_explicit_address_ignores_quoted_mentions(self): def test_claimed_message_uses_cutoff_and_aggregate_addressing(self): self.ingested.chat.is_private = False self.ingested.chat.reply_chance_percent = 0 + self.di.chat_membership_repo.save(domain.chat_membership( + user_id = self.ingested.author.id, + chat_id = self.ingested.chat.chat_id, + split_messages = False, + )) + other_member = self.di.user_repo.save(replace( + self.ingested.author, + id = uuid4(), + telegram_user_id = 987654321, + whatsapp_user_id = "other-member", + connect_key = "OTHER-MEMBER", + )) + self.di.chat_membership_repo.save(domain.chat_membership( + user_id = other_member.id, + chat_id = self.ingested.chat.chat_id, + split_messages = True, + )) self.di.chat_message_repo.save(domain.chat_message( chat_id = self.ingested.chat.chat_id, author_id = self.ingested.author.id, @@ -116,15 +135,35 @@ def test_claimed_message_uses_cutoff_and_aggregate_addressing(self): last_message_ingestion_order = self.ingested.message.ingestion_order, is_addressed = True, ) - self.model.responses.append(external.ai_message(content = "response")) + content = "First paragraph\n\nSecond paragraph\n\nThird paragraph" + self.model.responses.append(external.ai_message(content = content)) result = self.service.process_message(self.ingested, claim = claim) self.assertTrue(result) - self.assertEqual([message["text"] for message in self.telegram.get_sent_messages("123")], ["response"]) + self.assertEqual( + [message["text"] for message in self.telegram.get_sent_messages("123")], + [content], + ) self.assertNotIn("Future message outside this burst", str(self.model.prompts)) self.assertIn("hello", str(self.model.prompts)) + def test_delivery_failure_sends_separate_error_notification(self): + self.di.chat_membership_repo.save(domain.chat_membership( + user_id = self.ingested.author.id, + chat_id = self.ingested.chat.chat_id, + split_messages = False, + )) + self.model.responses.append(external.ai_message(content = "Undelivered response")) + self.telegram.delivery_errors_once["123"] = OSError("Delivery failed\n\nRetry later") + + self.assertFalse(self.service.process_message(self.ingested)) + + sent_texts = [message["text"] for message in self.telegram.get_sent_messages("123")] + self.assertNotIn("Undelivered response", sent_texts) + self.assertEqual(len(sent_texts), 1) + self.assertIn("```\nDelivery failed\n\nRetry later\n```", sent_texts[0]) + def test_reaction_response_is_stored_and_sent(self): self.model.responses.append(external.ai_message(content = "👍")) @@ -152,21 +191,41 @@ def test_failed_funded_response_sends_one_error_message_then_reacts(self): def test_whatsapp_message_marks_final_message_read(self): self.ingested.chat.chat_type = ChatConfigDB.ChatType.whatsapp - self.model.responses.append(external.ai_message(content = "response")) + initial_balance = self.ingested.author.credit_balance + price = self.di.messaging_price_service.get(self.ingested.chat.chat_type, self.ingested.author.whatsapp_user_id or "") + self.model.responses.append(external.ai_message(content = "First paragraph\n\nSecond paragraph\n\nThird paragraph")) self.assertTrue(self.service.process_message(self.ingested)) - self.assertEqual([message["text"] for message in self.whatsapp.get_sent_messages("123")], ["response"]) + self.assertEqual( + [message["text"] for message in self.whatsapp.get_sent_messages("123")], + ["First paragraph", "Second paragraph", "Third paragraph"], + ) + records = [ + record + for record in self.di.usage_record_repo.get_by_user(self.ingested.author.id) + if record.tool_purpose == ToolType.message_delivery + ] + self.assertEqual(len(records), 3) + self.assertEqual([record.api_call_cost_credits for record in records], [price] * 3) + self.assertAlmostEqual(self.di.user_repo.get(self.ingested.author.id).credit_balance, initial_balance - 3 * price) self.assertIn("message-1", self.whatsapp.read_messages) def test_whatsapp_read_receipt_failure_does_not_fail_delivered_response(self): self.ingested.chat.chat_type = ChatConfigDB.ChatType.whatsapp - self.model.responses.append(external.ai_message(content = "response")) + content = "First paragraph\n\nSecond paragraph\n\nThird paragraph" + + def respond_after_membership_removal() -> BaseMessage: + self.di.chat_membership_repo.delete(self.ingested.author.id, self.ingested.chat.chat_id) + return external.ai_message(content = content) + + self.model.responses.append(respond_after_membership_removal) with patch.object(self.whatsapp, "mark_as_read", side_effect = OSError("Read receipt failed")): self.assertTrue(self.service.process_message(self.ingested)) - self.assertEqual([message["text"] for message in self.whatsapp.get_sent_messages("123")], ["response"]) + self.assertEqual([message["text"] for message in self.whatsapp.get_sent_messages("123")], [content]) + self.assertIsNone(self.di.chat_membership_repo.get(self.ingested.author.id, self.ingested.chat.chat_id)) def test_delayed_attempt_replies_only_after_sleep_finishes(self): self.addCleanup(setattr, config, "chat_burst_quiet_period_s", config.chat_burst_quiet_period_s) diff --git a/test/features/integrations/test_integrations.py b/test/features/integrations/test_integrations.py index 4b25664f..4dea6b02 100644 --- a/test/features/integrations/test_integrations.py +++ b/test/features/integrations/test_integrations.py @@ -21,6 +21,7 @@ resolve_allowed_reactions, resolve_any_external_handle, resolve_best_notification_chat, + resolve_default_split_messages, resolve_external_handle, resolve_external_id, resolve_private_chat_id, @@ -59,6 +60,12 @@ def test_resolve_agent_user_whatsapp(self): self.assertEqual(agent.whatsapp_phone_number.get_secret_value(), config.whatsapp_bot_phone_number) self.assertEqual(agent.full_name, "The Agent") + def test_resolve_default_split_messages(self): + self.assertTrue(resolve_default_split_messages(ChatConfigDB.ChatType.telegram)) + self.assertFalse(resolve_default_split_messages(ChatConfigDB.ChatType.whatsapp)) + self.assertFalse(resolve_default_split_messages(ChatConfigDB.ChatType.github)) + self.assertFalse(resolve_default_split_messages(ChatConfigDB.ChatType.background)) + def test_resolve_allowed_reactions_telegram(self): reactions = resolve_allowed_reactions(ChatConfigDB.ChatType.telegram) self.assertIn("👍", reactions) diff --git a/test/stubs/api.py b/test/stubs/api.py index 70eb10b7..aa4cc4cd 100644 --- a/test/stubs/api.py +++ b/test/stubs/api.py @@ -128,6 +128,7 @@ def user_chat_config_payload(**overrides: Any) -> UserChatConfigPayload: defaults = { "use_about_me": True, "use_custom_prompt": True, + "split_messages": True, "max_output_tokens": 2_048, "max_chat_history_depth": 20, "max_iterations": 10, @@ -139,6 +140,7 @@ def user_chat_config_response(**overrides: Any) -> UserChatConfigResponse: defaults = { "use_about_me": True, "use_custom_prompt": True, + "split_messages": True, "max_output_tokens": 2_048, "max_chat_history_depth": 20, "max_iterations": 10, diff --git a/test/stubs/db.py b/test/stubs/db.py index 992a5cb5..505e7fba 100644 --- a/test/stubs/db.py +++ b/test/stubs/db.py @@ -125,6 +125,7 @@ def chat_membership_db(**overrides: Any) -> ChatMembershipDB: "is_admin": False, "use_about_me": True, "use_custom_prompt": True, + "split_messages": True, "max_output_tokens": 3_500, "max_chat_history_depth": 30, "max_iterations": 20, diff --git a/test/stubs/domain.py b/test/stubs/domain.py index 26b24e2e..eaa74c4b 100644 --- a/test/stubs/domain.py +++ b/test/stubs/domain.py @@ -292,6 +292,7 @@ def chat_membership(**overrides: Any) -> ChatMembership: "is_admin": False, "use_about_me": True, "use_custom_prompt": True, + "split_messages": True, "max_output_tokens": 3_500, "max_chat_history_depth": 30, "max_iterations": 20,