From e816bd9ae7259a2a270a54553cf1839e293faaff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 May 2026 08:16:25 +0000 Subject: [PATCH 01/10] Bump the pip-all group with 7 updates Bumps the pip-all group with 7 updates: | Package | From | To | | --- | --- | --- | | [uvicorn](https://github.com/Kludex/uvicorn) | `0.46.0` | `0.47.0` | | [requests](https://github.com/psf/requests) | `2.33.1` | `2.34.2` | | [langchain](https://github.com/langchain-ai/langchain) | `1.2.18` | `1.3.1` | | [langchain-core](https://github.com/langchain-ai/langchain) | `1.3.3` | `1.4.0` | | [resvg-py](https://github.com/baseplate-admin/resvg-py) | `0.3.1` | `0.3.2` | | [python-multipart](https://github.com/Kludex/python-multipart) | `0.0.27` | `0.0.28` | | [ruff](https://github.com/astral-sh/ruff) | `0.15.12` | `0.15.13` | Updates `uvicorn` from 0.46.0 to 0.47.0 - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.46.0...0.47.0) Updates `requests` from 2.33.1 to 2.34.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.33.1...v2.34.2) Updates `langchain` from 1.2.18 to 1.3.1 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain==1.2.18...langchain==1.3.1) Updates `langchain-core` from 1.3.3 to 1.4.0 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-core==1.3.3...langchain-core==1.4.0) Updates `resvg-py` from 0.3.1 to 0.3.2 - [Release notes](https://github.com/baseplate-admin/resvg-py/releases) - [Commits](https://github.com/baseplate-admin/resvg-py/compare/0.3.1...0.3.2) Updates `python-multipart` from 0.0.27 to 0.0.28 - [Release notes](https://github.com/Kludex/python-multipart/releases) - [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md) - [Commits](https://github.com/Kludex/python-multipart/compare/0.0.27...0.0.28) Updates `ruff` from 0.15.12 to 0.15.13 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.12...0.15.13) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all - dependency-name: requests dependency-version: 2.34.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all - dependency-name: langchain dependency-version: 1.3.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all - dependency-name: langchain-core dependency-version: 1.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all - dependency-name: resvg-py dependency-version: 0.3.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pip-all - dependency-name: python-multipart dependency-version: 0.0.28 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pip-all - dependency-name: ruff dependency-version: 0.15.13 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pip-all ... Signed-off-by: dependabot[bot] --- Pipfile.lock | 457 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 278 insertions(+), 179 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index fcd7158e..7ffdea70 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -927,11 +927,11 @@ }, "idna": { "hashes": [ - "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", - "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3" + "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", + "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc" ], "markers": "python_version >= '3.8'", - "version": "==3.13" + "version": "==3.15" }, "importlib-metadata": { "hashes": [ @@ -1083,12 +1083,12 @@ }, "langchain": { "hashes": [ - "sha256:7e829dbf117affadfd2067a0e97b4af20222f535f30fb812a28472d842c1074c", - "sha256:8432d43a65540845ed6f1a783d38d869c4659a6b9405f9a510169ad40d2f7bae" + "sha256:154e9c30c90b391eba4315296f6bf6b6fac6b058ddea4cc771a10470968fe36f", + "sha256:bc283c220233230f48b8e50ab1fbf1b688bcb206d933fa448d40a9b143177f62" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.2.18" + "version": "==1.3.1" }, "langchain-anthropic": { "hashes": [ @@ -1118,12 +1118,12 @@ }, "langchain-core": { "hashes": [ - "sha256:18aae8506f37da7f74398492279a7d6efcee4f8e23c4c41c7af080eeb7ef7bd1", - "sha256:fa510a5db8efdc0c6ff41c0939fb5c00a0183c11f6b84233e892e3227ff69182" + "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", + "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c" ], "index": "pypi", "markers": "python_full_version >= '3.10.0' and python_full_version < '4.0.0'", - "version": "==1.3.3" + "version": "==1.4.0" }, "langchain-google-genai": { "hashes": [ @@ -1179,27 +1179,27 @@ }, "langgraph": { "hashes": [ - "sha256:3115beb58203283c98d8752a90c034f3432177d2979a1fe205f76e5f1b744500", - "sha256:8a4f163f72f4401648d0c11b48ee906947d938ba8cf1f474540fe591534f0d17" + "sha256:03fd5895a8d4b70db1ff63ebc3bacead29dd20cd794a8b1a483e7ec9018f7a65", + "sha256:4a9baaf62afc5d5f63144a50095140a34b9aa9b7cea695d25326d564775348e7" ], "markers": "python_version >= '3.10'", - "version": "==1.1.10" + "version": "==1.2.0" }, "langgraph-checkpoint": { "hashes": [ - "sha256:a7b5e2ca18fb79b55edf19396d4ee446f8a53dcb7a4ec62ce6f1c7e00bb5af7f", - "sha256:b91b765712a2311a5b198760f714b7ab9b376d01c047ed78d9b9a3e80df802a3" + "sha256:8bc2a0466a20c38b865ce6671b42093fd5c041133f32351cae4222e0eeaf7fb5", + "sha256:e5bb304e30fc1363ac8fcb5f7dee5ca2185d77fe475b0d01de2c5f91324c2c21" ], "markers": "python_version >= '3.10'", - "version": "==4.0.3" + "version": "==4.1.0" }, "langgraph-prebuilt": { "hashes": [ - "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0", - "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456" + "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", + "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9" ], "markers": "python_version >= '3.10'", - "version": "==1.0.13" + "version": "==1.1.0" }, "langgraph-sdk": { "hashes": [ @@ -1211,11 +1211,11 @@ }, "langsmith": { "hashes": [ - "sha256:767ff7a8d136ed42926bf99059ac631dc6883542d6e3104b32e71c7625e1fa05", - "sha256:b2e40e308222fa0beb2dccee3b4b30bfee9062d7a4f20a3e3e93df3c51a08ab4" + "sha256:3615243d99c12f4047f13042bdc05a373dce232d106a6511b3ca7b48c5af1c2c", + "sha256:efc779f9d450dcaf9d97bc8894f4926276509d6e730e05289af9a64debce06ae" ], "markers": "python_version >= '3.10'", - "version": "==0.8.3" + "version": "==0.8.5" }, "lxml": { "hashes": [ @@ -2429,12 +2429,12 @@ }, "python-multipart": { "hashes": [ - "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", - "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602" + "sha256:10faac07eb966c3f48dc415f9dee46c04cb10d58d30a35677db8027c825ed9b6", + "sha256:8550da197eac0f7ab748961fc9509b999fa2662ea25cef857f05249f6893c0f8" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.0.27" + "version": "==0.0.28" }, "pyuploadcare": { "hashes": [ @@ -2664,12 +2664,12 @@ }, "requests": { "hashes": [ - "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", - "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a" + "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", + "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.33.1" + "version": "==2.34.2" }, "requests-toolbelt": { "hashes": [ @@ -2681,116 +2681,118 @@ }, "resvg-py": { "hashes": [ - "sha256:004b39716835042bf1b9c9908e8a5c07065c1998ef739dd65e8c259d619c594e", - "sha256:01bf85cee2d8a6569b43b74c767e33edb2e1e91585f3224e7a788437df0ebb92", - "sha256:033d52a565ac19939b92cd6e4e8507ac8430bb1b616a33a5957a74d383df4e96", - "sha256:07c7ef3eff4ad7c6ed1c73f3a88899e33f555b953c36e9106b3aa51b47197072", - "sha256:081ea2dcff269cb6919d2c6d9bb5f236b57a7b320558a0daaec6a3a5eafb5109", - "sha256:0d9f57794dde2757ab78e7eaa870a0053f139b95b933a61755af251886cefca9", - "sha256:1237fb10ff5f4bd37fa143e29be5b4717d478fe9c4b2cad6c6df8167f70fa8d5", - "sha256:12c90476f10de277c8d304ec8abd8370a10164f777a313f63c6a253609094d71", - "sha256:1468360377c1632552b81aeb51f652cd4621fe4a8af3b56f9fe26404e90acaa7", - "sha256:1791c79a3b528949d47de3481a654cdafff23a0d9dd0262ef6857e1b5696793a", - "sha256:17b99316937059fe17738d65473a4a99c8fa7ebc4dc0cfcf77b046b9ab74e706", - "sha256:1816ea93623667b2b6b2576c91eac5320ee626948ae974f5b8da30f3f8897fe3", - "sha256:1861c40d4aa1ab6a1ebc5d0ba01e75d15591542de4cc5ce0ede6f8d87ccdfc28", - "sha256:1948ccdfd6533ff7f16b7f6736d9578c3fdef4c4dc48e6b9f074629c378a09c1", - "sha256:19ecf7f02a723fa57ffbe61d2a8084247c6e809c142949f1c70b76163ead715a", - "sha256:2097dbbf6d4e49dc3f177cfe24d4eec74f9960694ee5142ca353ab7f0a6dd5d1", - "sha256:26f0726d26bf37aa834147bb8fe546e95483b72cd8c4076eef6b9114309bcc1b", - "sha256:26f318beb1b5f7b22c09856e10dc3f9e766b5090e09c5807998a8560dc478a49", - "sha256:2bee42d88f89b194897e1dd2845c1588ffabbdeddd7440b929de318cd84062ee", - "sha256:2e42527c311f293bf1d66f8f2710a9af7d33323b6f294deaa2db0450f37efc41", - "sha256:2ebe00ee61dfd884d7336bede836184c0dce3372d22eb26da91eebb894af7e6a", - "sha256:2fda98e05653c9bcd1468987b7d266e0f42fae31e337a157898b1b1745bb7066", - "sha256:3b52242e2726a335e0f9bf50a08c1d8557ac4b2dbe29cda75ea03471ebec8967", - "sha256:3e7aabda988380b81a3401863a3c51ea8dc0e2d33e162c8143c1720ebb306597", - "sha256:4135d2f73aa8e2964d86dcaf7f6c163e99266643224db482d1e1bb4a2e2934b2", - "sha256:43c487fe0703b1538571208837790c9a973c28393b66123d256e30cc9cb1746c", - "sha256:453b23c2cd797fe2fae9cba054e1960294132a9ed3a70e8790ae0d06afcaaa8b", - "sha256:485456303402d3ab562392967f89681702dcfe1d0ce13690602bb945c8b66939", - "sha256:4c2596e8db071841c03a532915ca15b0cb12a9d36bc7a816b667a232d2c6a28b", - "sha256:4d455955ca83b84f3d56fcd8c589782a25098e3b9bd6e54ac702b87e685d9f38", - "sha256:5014f10fc9540c40adcabfeaf16b65e0dcfe9bf933c4a196dac7378170b394ef", - "sha256:5219ff074e58a610c07d8a7c0aeaab6b34b5b898d26fb912a2fbd569d6805515", - "sha256:528116f9c9d6f0f01c951ecb1c2e524eacf10e873f6b61aee31d408cfca682e4", - "sha256:54452cfc3fbeb48e215d8ee64166677a2a15ec1a8c3e32e44092d5760422565c", - "sha256:55eec70718ba4ee3eb82a9e7e2d63fb3d88a67f3cd132932c824f901eca6e9d5", - "sha256:59aa3ee6e00030dfe74d0b9e6de66dff762fa94e86bc4f09c5b26583575c689f", - "sha256:5cc7cc08de6b21ac3acf4a3740b49a8b3e19a7002a0788faa4b1709b17697c6d", - "sha256:5e27c014a42f14a97da7e063479f949db3e6a504fd4fad0b536af284c395f3fd", - "sha256:5fd6c514472bb13e12bbba7152fcac68b0c8f03c9504461f501cbd5280523e7b", - "sha256:61a8c25d377fe0810474d780fc6dc4619546e3efc6363f0d5f3ec32c5d35c7a6", - "sha256:626a3e7528fe570162fe731a7fd5e5fb41a99c0ab8c30d4dd33b49447ef45a61", - "sha256:630de4ea28ccb3a32c057bf3dc83976c27a9f2121470fc49455474e8031c6622", - "sha256:68dc6cd44ca694178d26ccd8a165daaa6958bf746c7b5a9cd7c5a3c7630653c5", - "sha256:6932e79b0928b1eed4eee79bb8fb43c36fa406c368428e79744d3069240f423e", - "sha256:6f770da6e25b676c0c342430c8d726229ce5614f6367bf28016c92c0e4b83370", - "sha256:714d0c40c5487996decf67e35a639041f8e69bcbbf6f087a2bb3b7e6a1d54fec", - "sha256:75aafd045c99b5b78cf32951ddfd318f2b61120756c0476606ff05465baa04b2", - "sha256:780ecbcb421d29652a6be3fefdc3dd966ce56eb739715f996eace69096bdbf65", - "sha256:78ca7f042516f0075e2c7ed5030685dbcf531914834d1231b30459614a99634b", - "sha256:794f5ec38a6fe43ca70f7e4a212f65947da8fad91bcc753085f20d2223bb5b32", - "sha256:797aaa4cabfc3f9e1ba2e88e5550d8c40a2993d927c4ef0c5409289e3efbdee9", - "sha256:7a60300dd88468f41c63591dfa901e6f302a6c45111f2de581f34f47c85722f1", - "sha256:7e9f13f943563ecb3a4b637965c6ab382f651b881131cd15b2d453cf63ffe426", - "sha256:80b97d65aef33f896a3353d87f35a77b6957996be35c4edf3ea619e65978d7c3", - "sha256:834361bc462d86c8ed3e33236ba1f1ee090d1bccad22e2bd0f6502f3571a4dca", - "sha256:8798025dfcd0f45e37552817cfbab322722c249e5bada3c2a6fb6ef89941ad8d", - "sha256:8a3bdb35b88a86d4de826ac8dffff455a692d4d30254a6847c324996700a3c11", - "sha256:8d7ce446ccb94ecf20c411f64f5f84ec1a4c7d644a4f2e16c89c0c82fb492bcf", - "sha256:8e45a34731d69a5e3692d5fa44ded884dec6c9fba6e16f9fe15135ae5af0ffe0", - "sha256:943a6e535a20997f2ab9e40bf113ad2bc73cdb990cc204addbba6d0d25f4f4a5", - "sha256:996fee692d6d6f965bb928a3eeda7f97d9ee0517dbd8b3d15c5feb5fb554a624", - "sha256:9c5569ad0d7f186a65344332dc0adecbfe63e2185731fe7e1d3c8835ce34778a", - "sha256:9e20c5c5ee079d94c00e0c327f430c9f35e70a770f39267a95a90197ec6ae495", - "sha256:9e37670341cd92a81fd6bad4d25279dd68385abcca8c80e22ec88f71c0ea0ed3", - "sha256:a0f7317b8e8a475c61418c619520c0e552b0b44a8cacdee0976d43c0f923dc73", - "sha256:aaa0d91d19470302b21eca5c9828982b89e8dcb56c611e4378a21164db55d474", - "sha256:aace3614f8748dda64d31b7853e28625bf7beb3c3b1e8e4a5b95b2ca1f556d0d", - "sha256:ad752064570eacca66297dc0726ac9c0e0d4176000344aecea1e0eb1013ce667", - "sha256:aefe1359360207e4f3d36ea62937d0e2336a4f8c0a0949be6aec56e2e164a4bc", - "sha256:af4ef1da17c482e09d4e3a3fd18051225e5db6c274be17689ee3ff03a4b60c8c", - "sha256:b00dd9a26a72243686eeb3c8b326c91ba895474a097556ba73ca2cd48f082237", - "sha256:b06692bd45eec8c7ff50aaaca3d11638273e96cefaba45f1cf726234e6692631", - "sha256:b081f08ee0d8c13cad22c5c87371d883b30203123555558c0368ba491d4b304f", - "sha256:b2964ddbac5e35da4b41a151793798f12e6d90ee29993ad6a3c5c8bc0dc3f911", - "sha256:b4342aef07022684ede70271611316e69d6669fc06575bb3627402b0c4209583", - "sha256:b7ee9add07565b2f1d88767e703f630be5e500ebb74a8c4b15f4575379baf769", - "sha256:bafcf8039b79c18b443221bd74f7409edbaba080c30c184704ea3f5ed48df3c4", - "sha256:bb84b2a0d8b73d6f1d2a958bbfe19246d53ef241451e228ef09817b296a9d6a1", - "sha256:be53636e9e88d63922c41e681b2c98150b6c222b6f7747a61ada5a66b1592e03", - "sha256:c61ecf2cacf7225059fd7d3eba6aedf5e9410a49b187553022d79a4b7c1fcc19", - "sha256:c6c3083677c887e7f33f4446daadb997c4fa59e6551807463c11f046628d10e9", - "sha256:c6e439c4dd3ff4325cbd36280ea19d84d3054d80e087005bf4df306affb2d54a", - "sha256:c7b8c27f846b5a09e2a4aac926db38346eda7192d7daeacb8a7697531750affe", - "sha256:cb3fb18b82c2ea586a243897df5c9338438b9c87e6b1ae0ebf066a5c202fe6df", - "sha256:cc7d3fcc91ebadd5f07570d5f0903d96feed9963a6120b0024037ea7c4226c68", - "sha256:cc94a2e2d49ad8feb428861d1c9b8d94eecc97a9c136d424768605862e23725a", - "sha256:cd98461e13c5a6f37dc118af4df0de4edc4e66c4c1e2f585e7e34131314756ce", - "sha256:cec6446bbd9c537650273055c5fd2aa71f5b7827e0dc49a836759ddea04d025d", - "sha256:d23581f4f94032c1c871d9fa069d7e89e6ec8665c927e5f945bec435922e2daf", - "sha256:d2a743c0f20db581462922bdf5dad30d5aa553542df89352e31c107e2d4c3734", - "sha256:d7f0927afa87aae9eba04f6d10ea67e7892565f1fdcefc615cff3709f1917f75", - "sha256:db92ca05c37b622702bb92d28b4580620ecda597cd85fcfdc5ac6528d7f17524", - "sha256:dc83f52c931576aac2e18b1d3af49db49b7d58ebdb23dd2abd4f2ca69dda7b37", - "sha256:e21584d45937efade670eba6feca04ae0f4ec6ac183cc543f7d3eedf3a6970c6", - "sha256:e8ff4fc1468acedf2c127e22d107965d8f4a382aff31e2cbad3d0d7bf444e047", - "sha256:ea4dbd636f74a1e60bcc1e2fcc7788a8dcafc2802428d6f5e0ae7dc70a17dd54", - "sha256:ea9cb3f1b218f34dc64abec474cbffcaff301edece34602d6d2fb6418d4d0693", - "sha256:ec85a34f8ca40d9d1d86c261614cdf5a3b1ef96c73c4c2ef1d6cb93edb37a4ba", - "sha256:f3f7d017a72eeb6f87da4290dca558d529d7d73a2011adc1fd9ea9553c8f448c", - "sha256:f44b37d9a15a8012bd1793300e16462a6d19bc3aaf0ed125e244e48ec4ffe8d3", - "sha256:f515ba05ea6ee66abc54c824073a8cabcc91a8e8435cb549f7f3b68e51eadfbe", - "sha256:f7a7183b6c3e27157c7e00d0d9239a3bf1ff1b14724b87611992ed4ba21416fe", - "sha256:f9c62c6127726dc6d7a3a2488cb85dc0502a68c0da6164fc3383448590ddcedf", - "sha256:fa142d4255210b47c5891189b7318ed0efd19ddf1780c95d19a0b310a86b765e", - "sha256:faf570c2aed37be51897c009bf8ef2edd0a7c67694470b0aed97336ba4dce6be", - "sha256:fc10cba57eec3bdec8a4f0a15cbd352b85a5682e9119b88051c2265879b3aaed" + "sha256:040d70ae71cff3b0debe2a9d8d1ed4c39232dd0d43ea7f5801a6b56a35243f49", + "sha256:092fd27b5ee7d480a59c6c3c15a4a6ab551ab85909c41dd66f9ff1741e9f593e", + "sha256:0986d9150b603e534ba14e42ebb13952b18eb718491e92bf1e14e483c648596c", + "sha256:0f565ad3fe1bcf35b3bd1ba962dd3254cf2c7a30046c10160d71a098b7bb43af", + "sha256:10ac52b9f651d20d02952d1d08b8e26a1e487cae0a90de2ef4d3071cdb057a0a", + "sha256:1276bb7d4a0c73c1a6b9c08bae54d85affc934b762f5843d892a516d2c023d97", + "sha256:1866e2090eb0e493ca2732d37404f027ce262083322fc5a8d0d175dd20230e56", + "sha256:1a28a96aecde3253e086320fd04a7c59e6abfa9d0af28f0c9b0f867a82245bc3", + "sha256:1d13be473fcbe71a29e18263054b2d13f470b82a30aa24469d7dafe522058505", + "sha256:1d8b9973108f194d7ad752020e706dcccbb34e8f784024311fa1c0c01659b5c4", + "sha256:20e986c963f1bda4c1c42a01f89fa7653550f87ff8922da49773d07265a31f79", + "sha256:22a268e8be3b23279a8d9461a24d432587469d4811d3a142fbef310463255765", + "sha256:2f49de6c24a9faa773dc11f7f1b319b22ff4485fcf3d8b13c46c07d21c53bd49", + "sha256:2f5819f043811b81d1518f712ebafcd791abbaa479888d54b29cc764976868e6", + "sha256:3006f46efbb29a6c62ed3f22017814ea4cf99a190c9b69ea63287aefb12c8c66", + "sha256:30e4e92062c93e782506e54d39a96116a1eaaff5b0e08ff94c5f52452b061c75", + "sha256:31c562658dbb6f504c45a49120f0615c070cd28a8d9e1ca3704803e2e01d952e", + "sha256:332fbe99f5944db2ce0aefeaa29c8abd7e39a7179f8fde9c2475240aa7b14596", + "sha256:350eabcb016ef81cce93a1e3f5d9dae0f2f68a2760ba0925ab63c3f48c647c47", + "sha256:356a8522cdbafa7ef9a0546450489e02270a5a723ec50b4c5818b4f2b8f02893", + "sha256:356eed35c4eb9d21933e60ee75f798c0459702a97ed0a210404d7f7b9f032d3f", + "sha256:388b23701e40be279afe4e60da068fe3454dd43167584461c320a9f55a83a2bd", + "sha256:4004e7d1f3cab2000646400fc131388e2a86b092c3814288facd21bde54c7938", + "sha256:415509d213eaba339b4118a6e36cca7b6ee760b21703cdfdc8d9c934e30bb99b", + "sha256:42f7663406f4892f2df4a73c2546e1bc809b43fb5ac5d6665fe5d5f4600bb770", + "sha256:4772f64aceaf6777ef2a10a69f17b4a8db830d01f3bf092b2a094fe728131f83", + "sha256:4c40cf2109a15c83463038439fe70ae4b78025745be93a44bdb7a3bd43887981", + "sha256:4f05950f54ed4c33ed6d82668e75c7e4759a8f5e647f9c4b0db2bf4414a96962", + "sha256:4fdd946a0fbcc00423d2f7de3c95f84b5573d50011e515a9f1edf1518ae522e1", + "sha256:50163425cbe043ced2556817f2347aed01fba024cd7fe9dd6b7f49344f79eb54", + "sha256:508a4ed2d51e57904c46051835ed14651285d34be88542a048b977becadd2f32", + "sha256:5207bf2c246535ec9bdae28ba6cb92854f9511509ef1cb01733b1e9740198da8", + "sha256:57840840a1728003a2c7cde85f66241a6b9197746c9a02c374aa533ef2292ce3", + "sha256:5b9e324236898a3610907b2e7060c599fb6438a5e0e5c72f8314980eebf48be8", + "sha256:5c53cebc9baba81eabc5c8102048de5190b18252ae5c73ed2501e27ad2fda2fe", + "sha256:659df07ddde57b6755921069117dcd2bedcbc9de4e27a142a928dda98b562f59", + "sha256:65aa10f815e06ed063d93d949da481c73492fb6409260fa1e836ba523f9099e0", + "sha256:65b9ed1f42fde56211c4b1b47389beebd67861a971d65eba5e67e52b3e80f03d", + "sha256:65e9a6f6118be7a20fb57182da3f29b8c06ea329810e6041e7c7b245a0b6b7c1", + "sha256:66874a19209a905b278320e91739d8419f85870f9dda4fdaaf9bbd8dad83610f", + "sha256:6afef0dbcd30ad6a4e1be35fbb2c6706286fec70508e0246c9bdeea580ef4555", + "sha256:6b04e5d14977cef633b0a00d335f6727c9c27b024bf4a853d90277906ae2c390", + "sha256:6ed8bdb6a8d7539994511359ec657f00cd2c5f503c237cb24b9c5b309f64c9be", + "sha256:728507db685d949bf0bae7a255d36f645504b8891219e679511589c3d84cd576", + "sha256:7406be0435f91e33fefe4f6e56dd13a7d6bf178ba0ab7d60cd112404ebbac99c", + "sha256:7509b865a20223330b8b3517f6aec4613088ccd7155cd163105361d04120985e", + "sha256:7722c2652799b398450c5a926f4004220cc2c9b029ab97f1c9eed2cecbc1e735", + "sha256:7c459cdb4d1d0a5d9dbfee711f84fbf4551fba08754d21e085b3bd356f851fe1", + "sha256:7ce9e9ce3fdad8cb749688d12ecb5636d3acac9406e19517c46911ecb510eec6", + "sha256:7eefcbb18b1c86703eb3577cdcc5f6edb94c76dacb340a07b5a052e16047333a", + "sha256:7f19d15377b2d53b4a3fa6eeeee54774fd7a5642ea7e0ff91142ec295f988542", + "sha256:81b54cd02f11d83c82e278bdce7f71d674c6674ee60805c9764956b0492e7762", + "sha256:8398652c548e71c2bad914205be2735109e714ed0142a0c9ae3df1984fa3fb00", + "sha256:854a4bb16b8c61177ab543f2711c7b6a0d1f84fd3e8644aaf5e01587fe4c2750", + "sha256:86af2d12d74d949c9e38dccc7264014e503770eb27c678140a9d411a0c43ddc7", + "sha256:877a7b9056395c73644d8987ab93c99996aa3283843b49ad2a4443a3b44cf969", + "sha256:87e139d098ee82dc55092fc04da7e2a31cb100d63c7ea991bf8e679caf8b4b41", + "sha256:8c84a1053d319a0494fdf78432545460379adae6639aad6fb5f1f2e829940bc0", + "sha256:900aa5d6b04ce503e4114860aee24a10862d7bd2b164a5a7167af7a2769f08b6", + "sha256:91a7dd1bc684e947b8f11d5c2daf73b6e1a48d47e2f0be6e0f017797ae0dd0d3", + "sha256:94a1ae07976d963ade456f88ea63098fdc29bd1bea0df04c4d228588bc8fc621", + "sha256:966b0a12058c3f6f5956a7fdb52869f6cda4462868fb83f9d8eb412ffcc3b4e0", + "sha256:9750881a92863236e49776b97ccec8551835eca1a63fcb59baba97d30a2c3daa", + "sha256:985d08609be97976d9516788f26596a6a64ba3f786e13f3e2a90fb6f660a0b5f", + "sha256:991bccafe6487010f9b635e0c01620550d49f504020dbc4a5a75702efdfc8294", + "sha256:9cc5361e73e4f1ff7de8c65827d8e0de9577b9bab08684341ccf2fdeb1893426", + "sha256:9f59db5ef2cd7167b4ee389f528b159b9ce199dd9fa0e60c0227c3f24d00f401", + "sha256:a19034354e4af48993b9397123049344ab4766c2b44c72d5fdc0ee49c27023cc", + "sha256:a2010b59519128bc80055cf358e24c8c2b53550d7dd2a44742e9c2bc44e5e398", + "sha256:a2d4abb7ac0a4bba63f298a7203dc661b9344ff34771c997a0d0743d85c0cd35", + "sha256:a53346138595a2d97bb71597f4723cbba53db98372b1b080218076a9c44d1e3c", + "sha256:a79243683535fc4bf5d6617979109e6ad9ae555ebefb993622e1ddc81192a9fe", + "sha256:aa1dfb6e1ac37fefa55e9767459680069f58297902375106e201fcaedd481dcc", + "sha256:ab559b5845ed45aabd474710bff2d7a73a6bc6026b78fb73f958408fb373f5cd", + "sha256:acf2d47d0d8a7fea1314ad8e60f6f095ca352e9c28cb79074cd92ec72d89e8a8", + "sha256:ad547571eeeaaf45b6cef7aa49de575124533d1ab5f6275468f1a80c21d72d40", + "sha256:b1c40eb2ce447e8280c3eb644efb312c1307b481936f39c2f7281bac9a879216", + "sha256:b44b2b41bca6437976226a11acd41cb7b77fa06ed007d0953648d76d0ba6f4db", + "sha256:b65694c1c9b9f392255bdd61dd5b0d6a965bde923444957f6e0db6b287c95469", + "sha256:b68f7b11aac933952449925ff1fa381503a77260ffe12f902125f4581477a541", + "sha256:b9f7862cefd6f607690152acbfc98ae29ca82461e383fb0ad7fd5accc30034e5", + "sha256:ba4d141aa98ab05b8a1b0c8837acba0b1cb98429571db805c4d93855dedc9262", + "sha256:bcfa9e39c600420953bb3f4cf859eda0fc8cc070674b798fb1bcb41835e93852", + "sha256:bea4760b2b5ad416c1d5cb116d57118d37465df6bd6dce633e74b4dec7ac884e", + "sha256:becdb22ff66023b0f8f4f378a056e40c7d5a02c1ca81bd796591dd3ddffd756c", + "sha256:c1ae75c33fa328119a237f368df92209f41b7602c57ab8d2d67b899ea7a1dd97", + "sha256:c255b35424acdf3c66eda7e1384b780f6a90cb17c96bb6faadcd21fe93842542", + "sha256:c637b7dbd3fa5737b748ec0a9379b6be744698c51ce8c8dfab97a64e8cbd1964", + "sha256:cb74f863ac9d0997effbbd9e9c27b3c6df40f167f6a1cc5c1b982a8983e0fb36", + "sha256:cc776c4eddbee4e39f0db89ad14224a8d7206f3d0378f7b9bc1fbd2a4d1f78cd", + "sha256:ce4dd28bd82e2083a0efa8b9a26bb8a47c0a4d1ca894021a17f4d15963d9ec93", + "sha256:cf12829fcd960091293c7f9442e6a6e49eaacff8be9dc9f8ef6efbbd5f218b4e", + "sha256:cfebf7c2153bec45d8dc536b296f87570411fd85fbeed9cc2955d241ba17fdb8", + "sha256:d34bdf6bcf1cb6c431dfb202b4959626a9f8471972fa51d1dbdd2f0afc81eb75", + "sha256:d43d407ee684a302439ecc726e53000bb71fa8e22d2fa92c504383f58b056be5", + "sha256:da3326876bdfe93407c0afd9e6d61b8f78e4357cbb922e28aa4367bf10add2bc", + "sha256:da44618229009bf2e3f16a7c6283dba75b3ea753269bb57859eedd59c18959b3", + "sha256:ddda286dc5a1e9a2a4ae35a5b700b81eacf400c560c3032d59a40a45f5f4fbfb", + "sha256:deda8a5ca6a3d9d74c201c95727a9e53491171c56f747c9e938d9e72295257d8", + "sha256:e402791b3f83d086a9a96c28f34e7970d1a0047b5a51d65244052a4b17c80d74", + "sha256:eeba3965a0e102de8909a9370ec56e605a9006cc9ce3293edd26c3b3b782c6ba", + "sha256:f0d7844d80af88444e2c25398937dcb296427c1b3d5178cd7911db62210c87f3", + "sha256:f3dac8d29f09a86c46ac8cee8259276cf366e5031d43ab82f27b72d032633968", + "sha256:f812a79b07a94e7ab0f14a7ab7ba7d15cb97dc2f769efc3d66b707271818944a", + "sha256:f8a41da73f9fd456a3443f39967b087843b34e8b6e9f87e2989b09c58a7c796e", + "sha256:fc21bdd26a4a791c93192566f65dc1e521519973f4e92e2f0147aa9ddb9f3fec", + "sha256:fdda7269f2260f3ce6514b7a71f8cd13f957304f2d4e40a7dfbede0cccb357f5", + "sha256:fe2986e3096d8b35470141b4d29a53f3f46015d49f9f82b6104b8fc79bf9d819" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==0.3.1" + "version": "==0.3.2" }, "rsa": { "hashes": [ @@ -3015,40 +3017,137 @@ }, "uuid-utils": { "hashes": [ - "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", - "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", - "sha256:0b5d2ad28063d422ccc2c28d46471d47b61a58de885d35113a8f18cb547e25bf", - "sha256:12c65020ba6cb6abe1d57fcbfc2d0ea0506c67049ee031714057f5caf0f9bc9c", - "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", - "sha256:258186964039a8e36db10810c1ece879d229b01331e09e9030bc5dcabe231bd2", - "sha256:50fffc2827348c1e48972eed3d1c698959e63f9d030aa5dd82ba451113158a62", - "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", - "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", - "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", - "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", - "sha256:b197cd5424cf89fb019ca7f53641d05bfe34b1879614bed111c9c313b5574cd8", - "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", - "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", - "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", - "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", - "sha256:c1dbe718765f70f5b7f9b7f66b6a937802941b1cc56bcf642ce0274169741e01", - "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", - "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", - "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", - "sha256:da2234387b45fde40b0fedfee64a0ba591caeea9c48c7698ab6e2d85c7991533", - "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba" + "sha256:031baf2ce4136e98f68845d040683b83a64aac4f52c01830e066bbbe2a9113fc", + "sha256:04dafe5b74f9b9c27587001f39a256e981619626ddda20d7701d6b0a6c3cad51", + "sha256:05777b4e9a15b43707fba9789581bc39803172e7865e7c7932faf3e2f4299a4e", + "sha256:061a5d6f58e447ff41f13b07da83e0876cb4d9bcd5a83bf547db315abb886c0a", + "sha256:06a76000bd4917526549fedb63c417e1ea8e745388aedc9906d7af079f969668", + "sha256:072bacb159ded3c2c4c5b1b23191c72cb0906937816561fd6b71e8ab6612394e", + "sha256:0a177cdf2d86bb871e54528c0dfcf556021c8de61d4b10464018c5ffcce0d17f", + "sha256:0b4afc0774e43947c5d2338e83fbb30a9ff14bac20ab30528fd2be360716dbda", + "sha256:14481b7c98fbac536783d475b4d4cc7a4a21ec4f1ce794fc66557d3540b0c8b7", + "sha256:151dcf8aafd93d3747e6cac3d2de8173b4e8880b57db815fd51d945cb434afac", + "sha256:19f73783b7ab5a560368702f245bd550cd88e3b64ef33e689aebc67b51d782b3", + "sha256:1b48d6ca94783f5d3907717cea6a636e9451d3169d9398b287c81b18857c91b9", + "sha256:21af6cc771a769e4a8ef9ab245f7ee811a56fbcdd021e1163d845172a9c01e60", + "sha256:2422feb60039ce88daf02b9885665b060f0d2deb80a3debffaaedc443d9aa673", + "sha256:257335769b12ebd8c1ae809f8d22e5a4b829bdb9c796ce4f5a5f55d8bb76db86", + "sha256:270d7f11cfe821d68433103f63058d724c9165c2d1d443559f66cd67352748af", + "sha256:28f11e54c57bf5d9307d303777eea2b11cd1d2d2276f2975a2925ae92fb31dee", + "sha256:2e68c9d2927ab3b79892f6f9d857cffdb2043be33044854b05a84634ffdad88b", + "sha256:2ea58b9332ce8c04b8eec2c655b8bbd34ae31c06a5baf53f9a9b2324fc7d55a1", + "sha256:2f4b2f5b10f61ce498736b75c4f9fdb16b564ee92649f2ec41505e2584d86ff3", + "sha256:3070ba33b609299202e7e2ecfcfeb40451591874bcd4a6b268028d0f026bec49", + "sha256:30e7340f8b55f552a78d90eab2b2be6f68520c380215ddb7fb70a6d234ce154d", + "sha256:3334a5fdb5d5241c4f764382f01eeac6f56fc8fddf49924cd78a47e5c86ed329", + "sha256:348d913658b87da67dceab40fe2eead5e634c751d64fbe6d11edee878d8976ec", + "sha256:395ea1e40d6cd22bf6cfd00a3b25764571df783741d7a501f8b7a2d578f1148d", + "sha256:39a05db4e66ae5fe39b1d328446cdc560c29073dbe00c7abfea3d7a02dce62a1", + "sha256:3b4b001172dede7e0681c6e288ac7febf36efa3efcbe92a964ddcef4acdd9f7b", + "sha256:493a11c466e9e670bd8f3856f0c715d9bc12d27bf0510379fbbb21a10f38e553", + "sha256:4dbafbb3ee8828d3ef50414e4691e38b1202ce5f80c96a017f12a0821b8c791d", + "sha256:5050efb42112cd2dc37f8eb4efa65188b722dc60ae6e28a52845b5d27f35a85d", + "sha256:50cc685517e6b99be99b127e7f1817fbb65000d8816537852e603a2e3b60ac88", + "sha256:51c66955aeee2c284fb8cc5181e64587a63748e9835405de4b88f333f70a06fa", + "sha256:53b7c12e9ac48372781e6d409877621d1505955d8b37a505dbadb864f7098e85", + "sha256:5929aa92bf4ccb5456bd40646e3c45219cc8f1d751675af75f681674e7bd0029", + "sha256:5c29e29e8d5e9302cd84e4e5fdac38409448893048f42bd73d5e9b64d6eda2e4", + "sha256:5d721605af5478415b311b9d2bd7f3cc71d19dc071c7b891dc92221a845150d1", + "sha256:5e0cfdbde28603500ef6aa0187ba2b2fa7f0a6cfa417030d35bdc36df3d442b4", + "sha256:5ef6edbb10a4956755614e116aee4b558d75284b52dbedcf5f7505c518eb1011", + "sha256:60d5d7ef85592cdd555b01be4bc32b30a15854c3de99c5613e2e47299762b044", + "sha256:616a3e8f178c69f58d54d015bbb1666c6401ce3d41cc0473e67dfa278b96c8e5", + "sha256:62fd9267b40d82e2d9d148f560e86436f5b2daa9a1623c329ed0ec7e61fefc4d", + "sha256:640f427a8d5a0eb542113cd7e212a235883f0201c7c5184ef07a8e10fdaa5831", + "sha256:65fff497efacde5edf8627d59663a498f12f38e7eae51a7723dd881b5cf15ec7", + "sha256:66def20153b4d1e01c029863144bcbfc5c48397a0f7ff529562a9e756dbd89ab", + "sha256:670f174a447fa478605c48254f1b8f1fd309f1861be9fd469e5639230bc80ab7", + "sha256:6f3d38354ce3943fd721109c508b27a54147531ae656e675155301dfe25e8367", + "sha256:6fb8636100cd521325ac90a9c3ad6d4e6cc39ee39ce78bf757c014aaab79b780", + "sha256:6fcc9a560ec18c7b3a3db579a94ec9af25b2ea5683d93571580f98c5edd0a316", + "sha256:704c709d1054079a756e7baf0be2e76cb766d3fd2b3b6c71b1b758258c1d24e0", + "sha256:7169dd734319ea95e51894b61ad17e76b7edcf6927669ad3b963818e35e06086", + "sha256:73940debe273bf187301ed7650dcd72989f09523ced7a4bbb4ead0ca65e13d82", + "sha256:743fe546f8910edfd6a650cc4eb9995eb0d9dcfee11d948f5b326702851cb246", + "sha256:771f9db3cb3e5e3167beb7892ddcaf5d0440c5eff631f3b61476b607d7e59dab", + "sha256:775aa8fc68d448dc1d57a6da355f3c0e15b3b75f55e2245c43f04c43a5547c2d", + "sha256:7d06408fb951d187677d1ec5adf9073c873d818704be502e2ece178685a68bbf", + "sha256:7ea97b77218b431c4854f2ccd502819d78d1109188fccabaa005cff61c2ccc81", + "sha256:805c52f49bdb90a83727c80b97c98769ef68cc16f2a12ef6c41c4533633e8a95", + "sha256:80a23d5728d82666e788810d67f2dd57b209d4e95929d61d978e02d1d7ab27bc", + "sha256:81b8caec4b40925cbe2f0533af266cd9cd4485d94e48ecbb34663d5941c033aa", + "sha256:8399dd0dcfcb57db99090dae944644ba23151c57497226585f94926af9d93ae7", + "sha256:87b999e827a01681015068ae54c6c6ab8076b0f8bff6b4139eabdb2cd079d267", + "sha256:8b44795c09928ba55b15d94c4a2d29e942983eaf77f1bfa008ae596b5f1c72dd", + "sha256:90be3946ab215e180adb9827a90f9c63b6965af93b116c566f32e280bad6cccd", + "sha256:91d0f2a6d062b989491505ba1d50714cda62ad3826aa448ad5e23fe5fc9e0d5c", + "sha256:92076407ddb8b752df055378671b8c8bd3c6ffdb3064982190765b1fa685e624", + "sha256:92077d000654bbf64266b0043795ebe22de4e70c3f25f6c6297a8cd26279e709", + "sha256:93b30c7bcb148fa23497779ac53dfe34a0de6f53e300f6d585ac759e9e6718ef", + "sha256:97221ee09f9c97e9e32a5a468afa8b5d1440b65e7a57d4a0c2c9fe0546fc529b", + "sha256:98b74c6b46e0082c3b8ec2fbe1eb65376d8caf9ed2c903a457350d56260764c3", + "sha256:98bc52c15cf1baf602c965ecc2ed5d798cc8908084098ab6478b53a99b479fa8", + "sha256:9e0c1d03e7d245f03d968f1da709e396f37f56495e231a22bd47f94ab6ae8827", + "sha256:a11e885489a12b8fcf71fcfe7e1ae078515574e9a102f0819f189a4d62db301e", + "sha256:a1a02705b659a2b9874de0e2187f0c64277e14dae3299b392f0c46c762bd1144", + "sha256:a30412da63cc484bc7e132f4362b4b44ea7dc1ec19ca33378c9bf9f64c5e294d", + "sha256:a8ab927c4bec80e4b784c5c9af7ce1c74f22b80abc6db2895fe18268255a0060", + "sha256:a8fb2aad5bb6256324de967bbf86f2227884586c3598a3e14fd5c339d3bfc20f", + "sha256:ab7a1bf10777953c375e8525bd7070072566c8b247ffc4d3c082dad5f1a66e86", + "sha256:ad134557819143c37ebd0eecf058accba94664ff4d50ef8bf619a255bdafdcea", + "sha256:b3f0e567b5e992b28a50f50e0aeba546a2e2d3e463590eb5543204cb5d0f40b3", + "sha256:b6326c3aff73342b50a39af0301972b671f1da68e6f2d88aaf5b959489b0c0a1", + "sha256:b69f59d0012fe4b94192716ae8c4a8acfe72c97b429aed69511d3096830c3e98", + "sha256:b96ffa58744f62dd6dd9c5ed33f8e6232a90e710aeb46758f3776d904352f755", + "sha256:ba5bc9191c5636bf2bc33d81166c0b27a71ff1b19ab881a8c80bd70f86578a3d", + "sha256:bc9cf4c4a7058f06b67b8cf81f228ccd80ba1ef506e875eed33d05ff19e9a32b", + "sha256:bceb8aefc5c26ed896f93a36344ff476085f340d051a73074603426ef7588e4d", + "sha256:be62c176390690b9c28b2cfd5ae8fb1f1d469c76ff85348912904f000d6576fd", + "sha256:bfaab7ec64936ceae273ec195673acbee247d69525a2186159360d46d54819a0", + "sha256:c0960c0475033bab0dddb13919e627c062d83d17900f22206c59b2942fe03703", + "sha256:c40cb6a68b95787a55d401394178213003dfce1e6e62d1097756a5fb70aae9da", + "sha256:c4835b0907466a535b255a27df6cf0d37ea4ab4b69edde53cc350563e8b55442", + "sha256:c48befbd21e0d9dbb4aeea6bc6b515b14aadd5804a997349b061bed285813d39", + "sha256:c6435d27f2d541506590ea3db6ab92701bad24652678e1b6b2d8e48d8888152b", + "sha256:c64ea2623efa56fc2e66ccdffd7bcfcf794f5c25f50dcf6f6eb9a041ad73cc47", + "sha256:c78462302d81e1d7f7fb0ee14ff7c521e47a27c4d7222a4933c01a431d2a6efc", + "sha256:c97357517e59bf767c7e0d13e9fe02c26f241b4ebf297c7479b100fea277c0b2", + "sha256:cb9cc99885b676d0f5ce8e0996b57ba2a53fe3a3f0163c7c9e06151e0232982f", + "sha256:cf10ff623989cf07e63b492fc025b43302eea33c6f6e986bc2ffd3f60a21da01", + "sha256:d3d1a1ede7d85f80cbad381f8a09467f083b3bd9978f3daa32cc8b6f09cdc3fe", + "sha256:d4f797414c036c7b7c862d6401da8bcbfd19086eabb41035c468e0ad564d339e", + "sha256:d6b61e5f201535b525956817e3f8a09a90ec5b7d389b5a511b4f985427f23476", + "sha256:d6c34e3f5f7f49f048165c3ac6b5a4515e17c4b96a261bb4aaabecca83de7412", + "sha256:d9d8af23f0a3a66ee061952ef01dec6d01ba611b62e15e72239787acf2a2ee6d", + "sha256:dba4c5522b26d0ce1435544cd3beeef35ac485de3fc72a43b91b43b3a4315ab8", + "sha256:dc391e241f9b3d98901c5ada27546ddb49b71f1ad2f9dfe41cd91d6d69d84156", + "sha256:deee61ce9447f63e6ec765484b40f77dadac9672fb5c49d5f5586d93df38ae85", + "sha256:e1e2f4a8ca70ff617916719eadb1f148cc6eb65a4b2b89f35422bf9d595461aa", + "sha256:e25b270f98dcf395a434bec704cb503516a71519198634bc827ba87a584387f7", + "sha256:e5763f07d99e2237742ebd0155ca18c1c8233de457c9e8e59bdc4d130895d15a", + "sha256:e621fddb104f48d32cab850f31dfe9c0cb60655176a726905d13423d6690910c", + "sha256:e6c7ed64c69f815cf434384681d64ee5aa574160a8e2d2a9a63088d388cb8ae7", + "sha256:eb92af15ee1a8f00d693027f4e8a9d2cf4a5cae57e324c854246e7a6147aed6a", + "sha256:ebacba63d31afbea72e5bf12205413a5f53a2654c9f6302abf8de7cc6697a4d8", + "sha256:ee30f2c859c3ea7c78c42c1a3859953952e2eb58725548af0b5185cd0e4522a5", + "sha256:f182733e3d88edd2ceeca292627e2b1d5fa8693abe00b160de5517616ed399ea", + "sha256:f51f8f74f65b1a8f0cecccd2ab8d04c28df82e813e83cd29248c6a0a9cb96b71", + "sha256:f76f5654441960425726e377e3ecfaa9e14cde3cc9b2e9f673bbb11daa38e1c3", + "sha256:fb191897ac7c1e759561f85e66dcd6312d85c5a2316862906adce4e701c4efb0", + "sha256:fc9a85207269b436255b08f504e3ea185f6f1e4813ffa43c0e658a63af99e7e6", + "sha256:fe2c06496431a216b63eab7787454c4d8a6353379d2cdf605c843c9b231fad5b" ], "markers": "python_version >= '3.9'", - "version": "==0.14.1" + "version": "==0.15.0" }, "uvicorn": { "hashes": [ - "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", - "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d" + "sha256:2c5715bc12d1892d84752049f400cd1c3cb018514967fdfeb97640443a6a9432", + "sha256:7c9a0ea1a9414106bbab7324609c162d8fa0cdcdcb703060987269d77c7bb533" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.46.0" + "version": "==0.47.0" }, "webencodings": { "hashes": [ @@ -3932,28 +4031,28 @@ }, "ruff": { "hashes": [ - "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", - "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", - "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", - "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", - "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", - "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", - "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", - "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", - "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", - "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", - "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", - "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", - "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", - "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", - "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", - "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", - "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", - "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5" + "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", + "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", + "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", + "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", + "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", + "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", + "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", + "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", + "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", + "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", + "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", + "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", + "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", + "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", + "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", + "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", + "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", + "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.15.12" + "version": "==0.15.13" }, "the-agent": { "editable": true, From 915ad4dc56abc850a82f057f0a780c809507934e Mon Sep 17 00:00:00 2001 From: the-agent-abot Date: Sat, 16 May 2026 08:17:27 +0000 Subject: [PATCH 02/10] Auto-lock: Update dependencies --- Pipfile.lock | 540 +++++++++++++++++++++++++-------------------------- 1 file changed, 270 insertions(+), 270 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 7ffdea70..325f1901 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -187,11 +187,11 @@ }, "anthropic": { "hashes": [ - "sha256:1c15769efa15d8fd5c1ebf900e25c57e3ee540f8554a29aa56e4edefffe2951d", - "sha256:650dee9e023afb16395939ee4104bbc21f966b380210119fb91122c12099c79a" + "sha256:96f747cad11886c4ae12d4080131b94eebd68b202bd2190fe27959031bb1fa9c", + "sha256:ab96540bbd4b0f36564252d955a86f8abbe4f00944a24bc9931acc9b139bab6f" ], "markers": "python_version >= '3.9'", - "version": "==0.100.0" + "version": "==0.102.0" }, "anyio": { "hashes": [ @@ -719,11 +719,11 @@ "requests" ], "hashes": [ - "sha256:01f30e1a9e3638698d89464f5e603ce29d18e1c0e63ec31ac570aba4e164aaf5", - "sha256:aee92803ba0ff93a70a3b8a35c7b4797837751cd6380b63ff38372b98f3ed627" + "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68", + "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c" ], "markers": "python_version >= '3.10'", - "version": "==2.52.0" + "version": "==2.53.0" }, "google-genai": { "hashes": [ @@ -1630,89 +1630,89 @@ }, "numpy": { "hashes": [ - "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", - "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", - "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", - "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", - "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", - "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", - "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", - "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", - "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", - "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", - "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", - "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", - "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", - "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", - "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", - "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", - "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", - "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", - "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", - "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", - "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", - "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", - "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", - "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", - "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", - "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", - "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", - "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", - "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", - "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", - "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", - "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", - "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", - "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", - "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", - "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", - "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", - "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", - "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", - "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", - "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", - "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", - "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", - "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", - "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", - "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", - "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", - "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", - "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", - "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", - "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", - "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", - "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", - "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", - "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", - "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", - "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", - "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", - "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", - "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", - "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", - "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", - "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", - "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", - "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", - "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", - "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", - "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", - "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", - "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", - "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", - "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e" + "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83", + "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be", + "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864", + "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd", + "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566", + "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8", + "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6", + "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38", + "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c", + "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361", + "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3", + "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080", + "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83", + "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7", + "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277", + "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed", + "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5", + "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb", + "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db", + "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097", + "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288", + "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72", + "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6", + "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29", + "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20", + "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453", + "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0", + "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7", + "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa", + "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f", + "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862", + "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf", + "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507", + "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4", + "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef", + "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912", + "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95", + "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419", + "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97", + "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3", + "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef", + "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb", + "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a", + "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70", + "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a", + "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b", + "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976", + "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656", + "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5", + "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61", + "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77", + "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c", + "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222", + "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40", + "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685", + "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f", + "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7", + "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc", + "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f", + "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963", + "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772", + "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b", + "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059", + "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25", + "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198", + "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957", + "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23", + "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6", + "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55", + "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce", + "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42", + "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33" ], "markers": "python_version >= '3.11'", - "version": "==2.4.4" + "version": "==2.4.5" }, "openai": { "hashes": [ - "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7", - "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63" + "sha256:814633888b8f3b1ffd6615697c6e4ef93632d08b7c2e28c8c5ef3556e5a10107", + "sha256:f4bc562cc5f3a43d40d678105572d9d44765f6e0f50c125f63055419b72f4bd9" ], "markers": "python_version >= '3.9'", - "version": "==2.36.0" + "version": "==2.37.0" }, "opentelemetry-api": { "hashes": [ @@ -1884,11 +1884,11 @@ }, "perplexityai": { "hashes": [ - "sha256:b03503498591d06c4d50b666f7f7469875d3586f664c29416aae9012ae7a64d1", - "sha256:e5017d245fd8966cf79657edc03a93078d867708542b491b38152618f91e369b" + "sha256:c2f70eaaa8b51681e2e9a17b0d0f30f573739641cc2f639fa1c926a48eb9497f", + "sha256:df08280b320ff755777d5d044f74d03fed44789337ad16ed6d75f07619e64a03" ], "markers": "python_version >= '3.9'", - "version": "==0.32.1" + "version": "==0.34.0" }, "pillow": { "hashes": [ @@ -2535,123 +2535,123 @@ }, "regex": { "hashes": [ - "sha256:011bb48bffc1b46553ac704c975b3348717f4e4aa7a67522b51906f99da1820c", - "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", - "sha256:0540e5b733618a2f84e9cb3e812c8afa82e151ca8e19cf6c4e95c5a65198236f", - "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", - "sha256:0709f22a56798457ae317bcce42aacee33c680068a8f14097430d9f9ba364bee", - "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", - "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", - "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", - "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", - "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", - "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", - "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", - "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", - "sha256:1b9a00b83f3a40e09859c78920571dcb83293c8004079653dd22ec14bbfa98c7", - "sha256:21e5eb86179b4c67b5759d452ea7c48eb135cd93308e7a260aa489ed2eb423a4", - "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", - "sha256:2895506ebe32cc63eeed8f80e6eae453171cfccccab35b70dc3129abec35a5b8", - "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", - "sha256:2a5d273181b560ef8397c8825f2b9d57013de744da9e8257b8467e5da8599351", - "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", - "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", - "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", - "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", - "sha256:312ec9dd1ae7d96abd8c5a36a552b2139931914407d26fba723f9e53c8186f86", - "sha256:33424f5188a7db12958246a54f59a435b6cb62c5cf9c8d71f7cc49475a5fdada", - "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", - "sha256:33bfda9684646d323414df7abe5692c61d297dbb0530b28ec66442e768813c59", - "sha256:349d7310eddff40429a099c08d995c6d4a4bfaf3ff40bd3b5e5cb5a5a3c7d453", - "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", - "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", - "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", - "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", - "sha256:415a994b536440f5011aa77e50a4274d15da3245e876e5c7f19da349caaedd87", - "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", - "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", - "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", - "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", - "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", - "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", - "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", - "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", - "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", - "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", - "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", - "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", - "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", - "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", - "sha256:6780f008ee81381c737634e75c24e5a6569cc883c4f8e37a37917ee79efcafd9", - "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", - "sha256:6aa809ed4dc3706cc38594d67e641601bd2f36d5555b2780ff074edfcb136cf8", - "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", - "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", - "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", - "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", - "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", - "sha256:74fa82dcc8143386c7c0392e18032009d1db715c25f4ba22d23dc2e04d02a20f", - "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", - "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", - "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", - "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", - "sha256:7d346fccdde28abba117cc9edc696b9518c3307fbfcb689e549d9b5979018c6d", - "sha256:8512fcdb43f1bf18582698a478b5ab73f9c1667a5b7548761329ef410cd0a760", - "sha256:867bddc63109a0276f5a31999e4c8e0eb7bbbad7d6166e28d969a2c1afeb97f9", - "sha256:88e9b048345c613f253bea4645b2fe7e579782b82cac99b1daad81e29cc2ed8e", - "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", - "sha256:9542ccc1e689e752594309444081582f7be2fdb2df75acafea8a075108566735", - "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", - "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", - "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", - "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", - "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", - "sha256:a0d2b28aa1354c7cd7f71b7658c4326f7facac106edd7f40eda984424229fd59", - "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", - "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", - "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", - "sha256:a85b620a388d6c9caa12189233109e236b3da3deffe4ff11b84ae84e218a274f", - "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", - "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", - "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", - "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", - "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", - "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", - "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", - "sha256:b4c36a85b00fadb85db9d9e90144af0a980e1a3d2ef9cd0f8a5bef88054657c6", - "sha256:b5f9fb784824a042be3455b53d0b112655686fdb7a91f88f095f3fee1e2a2a54", - "sha256:be061028481186ba62a0f4c5f1cc1e3d5ab8bce70c89236ebe01023883bc903b", - "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", - "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", - "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", - "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", - "sha256:cf9b1b2e692d4877880388934ac746c99552ce6bf40792a767fd42c8c99f136d", - "sha256:d2228c02b368d69b724c36e96d3d1da721561fb9cc7faa373d7bf65e07d75cb5", - "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", - "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", - "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", - "sha256:dcb5453ecf9cd58b562967badd1edbf092b0588a3af9e32ee3d05c985077ce87", - "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", - "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", - "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", - "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", - "sha256:e355be718caf838aa089870259cf1776dc2a4aa980514af9d02c59544d9a8b22", - "sha256:e7ab63e9fe45a9ec3417509e18116b367e89c9ceb6219222a3396fa30b147f80", - "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", - "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", - "sha256:eb59c65069498dbae3c0ef07bbe224e1eaa079825a437fb47a479f0af11f774f", - "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", - "sha256:ee9627de8587c1a22201cb16d0296ab92b4df5cdcb5349f4e9744d61db7c7c98", - "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", - "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", - "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", - "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", - "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", - "sha256:fe896e07a5a2462308297e515c0054e9ec2dd18dfdc9427b19900b37dfe6f40b", - "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e" + "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", + "sha256:01f0f5f55f4b64dacec85dc116d3c05fd23ad3ff037bbc73a2085775953c2611", + "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", + "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", + "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", + "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", + "sha256:0f03aa6898aaaac4592479821df16e68e8d0e29e903e65d8f2dfb2f19028a989", + "sha256:0f9eede6a5cbdc02d4978090186390936e1776a7d1359b21e41014c609880bcf", + "sha256:1268eddd8486dc561d08eee1156e40aa3a8fe10f4bdec8fa653b455fcbffd12c", + "sha256:15ee42209947f4ca045412eae98416317238163618ace2a8e54f99586a466733", + "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", + "sha256:19c16ceb4a267a8789e25733e583983eeab9f0f8664e66b0bd1c5d21f14c2d4b", + "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", + "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", + "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", + "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", + "sha256:246de9d60aa3f8538b519834dd95cbf276ea263d6a7bd5a3666dc3fa0230505b", + "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", + "sha256:2a661a7d270a61f7cf460caee8b9fa2d5ef9e5c681234bcb9e0fe14f488e7dfc", + "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", + "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", + "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", + "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", + "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", + "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", + "sha256:3b1e39888c5e0c7d92cea4fc777396c4a90363b05de75d02eb459a4752200808", + "sha256:3dd4a3ff360dfb836fecdb93a4598f9d6e2ac81e3e397125145c6221bf58cf4c", + "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", + "sha256:446ddd671e43ab535810c4b21cff7104945c701d4a14d1e6d1cd6f4e445a8bea", + "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", + "sha256:46f1326ca6e65b0879d23ca302c0f2415aad42ff0309b9c818e7949fe19a41d8", + "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", + "sha256:4ebe8f0b5ec5a5024dc4a4c59f444c4e9afc5f2abdbb8962065b75d27fb971f9", + "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", + "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", + "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", + "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", + "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", + "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", + "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", + "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", + "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", + "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", + "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", + "sha256:6ba42b2e7e7f46cf68cc6a5ca36fa07959f9bbd9c6bdcc47b6ee76549a590248", + "sha256:71b61c5bfe1c806332defc42ad6c780b3c55f661986d7f40283a3a88274b4c00", + "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", + "sha256:7b92817338591505f282cf3864c145244b1edcf5381d237038df955001091538", + "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", + "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", + "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", + "sha256:8676474c07469d6f33dd1085ca2cd45f65785f32518f2b20e36d9953ca07f994", + "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", + "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", + "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", + "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", + "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", + "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", + "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", + "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", + "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", + "sha256:954cc214c04663ee6d266fc61739cad83054683048de65c5bd1d640ad28098ac", + "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", + "sha256:97cf3bc1b7d7d2306772ec07366c80d9df00ff79e79cea32898883a646d2fae2", + "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", + "sha256:992604d02e6d9c6d786c24a706a71ecffe1020fc1ef264044474cd81fa2c3919", + "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", + "sha256:a6a563446a41adc451393dc6b8e6ad87979efaee3c8738690a8d1b08ebead1b4", + "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", + "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", + "sha256:a9e1328e17c84c1a5d22ec9f785ecef4a967fab9a42b6a8dc3bcbebd0a0c9e44", + "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", + "sha256:b310768746dd314ea6e2ff4cc89ef215426813396ff4e94ee8e6f7096c8b6e03", + "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", + "sha256:b4bb445ff3f725f59df8f6014edb547ee928ec7023a774f6a39a3f953038cbb2", + "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", + "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", + "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", + "sha256:bfe1ce50cbfb569d74e1e4337da6468961f31dbea55fd85aa5de59c0947a805a", + "sha256:c010eb8caca74bdb40c07498d7ece26b4428fd3f04aa8a72c9ac6f79e8faaac6", + "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", + "sha256:c9411dd64ca95477225734a93dfc8583b51916b8d5942f99d6cac21e09965451", + "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", + "sha256:ccf5249114cc3e772ecdd88a98a86eca0fd74c61ce32a94743758c083fc05d48", + "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", + "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", + "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", + "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", + "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", + "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", + "sha256:d6b8a143aca6c39b446ea8092cde25cc8fe9304d4f5fecfbc1a9dbb0282703c2", + "sha256:d726ca3f0d76969bf1e8e477d160d3d666bbf999f6860bd314889e5345782046", + "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", + "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", + "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", + "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", + "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", + "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", + "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", + "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", + "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", + "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", + "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", + "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", + "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", + "sha256:ed457d8e98ae812ed7732bef7bf78de78e834eae0372a74e23ca90ef21d910f9", + "sha256:ef31cbfe458e21c6122ba8150ff060e0c7789ed0d26eb423f25472584920b555", + "sha256:f079e50a0d3cc3cd5091fa9ff45869a2e6b2cd35895731edafb0327901a8d86d", + "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", + "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", + "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", + "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", + "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", + "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763" ], "markers": "python_version >= '3.10'", - "version": "==2026.4.4" + "version": "==2026.5.9" }, "replicate": { "hashes": [ @@ -2914,66 +2914,66 @@ }, "tiktoken": { "hashes": [ - "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", - "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", - "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", - "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179", - "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", - "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec", - "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946", - "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", - "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", - "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3", - "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", - "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", - "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970", - "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", - "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", - "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", - "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7", - "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", - "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", - "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", - "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", - "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", - "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", - "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134", - "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", - "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a", - "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1", - "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", - "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892", - "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", - "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", - "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", - "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", - "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", - "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", - "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", - "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", - "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", - "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16", - "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", - "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", - "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", - "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", - "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", - "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030", - "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", - "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e", - "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", - "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", - "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", - "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c", - "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", - "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", - "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", - "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", - "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", - "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd" + "sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4", + "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58", + "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2", + "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f", + "sha256:2a3b536c55802fe42f4b4644d2be4f04bf788506b48de0a0a658cb58f8bce232", + "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a", + "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b", + "sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff", + "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791", + "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881", + "sha256:35e1ea1e0631c04f551297284a1ab7e1f65a3c55a9a48728d5e0f66b4527c04a", + "sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173", + "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7", + "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a", + "sha256:44733b99bfd72b590cd0936b1c01b3b4dd73122db2d544bc1ceeb18a7678c910", + "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b", + "sha256:477c9a38e20d0ed248090509acf1e839ad3967a4f00b4b0f958210049f656dee", + "sha256:47b1df8d73390a24f94980c75158cdd5c56d256f16d55f30cb49c230caba9ba4", + "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad", + "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b", + "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448", + "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce", + "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24", + "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424", + "sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed", + "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154", + "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf", + "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e", + "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7", + "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec", + "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67", + "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615", + "sha256:7ab10f4a21c2999846940113f6dbd72e0fa06a24119feddd74cc47e85818e06d", + "sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb", + "sha256:7d40c6c5aab171dcd6eb8455bc567bde404bb9def60cdb8c1299cc782b242bb9", + "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d", + "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", + "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41", + "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545", + "sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26", + "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91", + "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486", + "sha256:9b842981fa91accdffd48ff6408a977b7a91c3fbda55d353c3c68114d5c9d69e", + "sha256:9b8858b29804b3a0add25ce9e62fb00f89f621dc754d75d03ca419d17e8ddf67", + "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649", + "sha256:a2937ad042d49d50eac6e1ba07c5661d4bd3942a5b1e0c0d08475c4df83676e1", + "sha256:b8ac2d6420ff05841a89ba5205c6d45f56c4f6843454f3c884b7eb1a2a8dddb2", + "sha256:b967dfb9d0adf9a631953b1b40717684f04478270fc51bbccdd2f838d67a2f00", + "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1", + "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd", + "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51", + "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273", + "sha256:da86f8c96ac1c235d7a3b3eebff1eacfdbcfb8ad792706943268d4d2938fbafe", + "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2", + "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471", + "sha256:ed5a30027cb4d8c7ca8b273d4766f3db3cf58fad9e9f3b1a68a351ffb54873d5", + "sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94" ], "markers": "python_version >= '3.9'", - "version": "==0.12.0" + "version": "==0.13.0" }, "tqdm": { "hashes": [ @@ -3850,11 +3850,11 @@ }, "idna": { "hashes": [ - "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", - "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3" + "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", + "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc" ], "markers": "python_version >= '3.8'", - "version": "==3.13" + "version": "==3.15" }, "iniconfig": { "hashes": [ @@ -3926,11 +3926,11 @@ }, "python-discovery": { "hashes": [ - "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f", - "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0" + "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", + "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c" ], "markers": "python_version >= '3.8'", - "version": "==1.3.0" + "version": "==1.3.1" }, "pyyaml": { "hashes": [ @@ -4013,12 +4013,12 @@ }, "requests": { "hashes": [ - "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", - "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a" + "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", + "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.33.1" + "version": "==2.34.2" }, "requests-mock": { "hashes": [ @@ -4069,11 +4069,11 @@ }, "virtualenv": { "hashes": [ - "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b", - "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35" + "sha256:7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3", + "sha256:f5bda277e553b1c2b3c1a8debfc30496e1288cc93ce6b7b71b3280047e317328" ], "markers": "python_version >= '3.8'", - "version": "==21.3.1" + "version": "==21.3.3" } } } From c82a0ce1939a0c58cd0a0841075e37d168419592 Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:44:56 +0200 Subject: [PATCH 03/10] Add support for external media URL processing in chats --- src/db/model/chat_config.py | 2 +- src/di/di.py | 17 ++- .../chat/chat_attachment_processor.py | 26 +--- src/features/chat/chat_attachment_utils.py | 30 ++++ src/features/chat/chat_image_edit_service.py | 10 +- .../chat/llm_tools/llm_tool_library.py | 46 ++++-- src/features/chat/url_attachment_resolver.py | 67 +++++++++ src/features/prompting/prompt_library.py | 5 + .../web_browsing/html_content_cleaner.py | 57 +++++++- src/util/error_codes.py | 1 + .../chat/test_chat_attachment_processor.py | 73 +++++++++- .../chat/test_chat_attachment_utils.py | 137 ++++++++++++++++++ .../chat/test_chat_image_edit_service.py | 95 ++++++++++-- .../chat/test_url_attachment_resolver.py | 94 ++++++++++++ .../web_browsing/test_html_content_cleaner.py | 37 ++++- 15 files changed, 640 insertions(+), 57 deletions(-) create mode 100644 src/features/chat/chat_attachment_utils.py create mode 100644 src/features/chat/url_attachment_resolver.py create mode 100644 test/features/chat/test_chat_attachment_utils.py create mode 100644 test/features/chat/test_url_attachment_resolver.py diff --git a/src/db/model/chat_config.py b/src/db/model/chat_config.py index 8bb49960..25aec7ca 100644 --- a/src/db/model/chat_config.py +++ b/src/db/model/chat_config.py @@ -120,7 +120,7 @@ def __ge__(self, other): title = Column(String, nullable = True) is_private = Column(Boolean, nullable = False) reply_chance_percent = Column(Integer, nullable = False) - release_notifications = Column(EnumSQL(ReleaseNotifications), nullable = False, default = ReleaseNotifications.all) + release_notifications = Column(EnumSQL(ReleaseNotifications), nullable = False, default = ReleaseNotifications.major) media_mode = Column(EnumSQL(MediaMode), nullable = False, default = MediaMode.photo) chat_type = Column(EnumSQL(ChatType), nullable = False) diff --git a/src/di/di.py b/src/di/di.py index ed95d1d9..dd60c03e 100644 --- a/src/di/di.py +++ b/src/di/di.py @@ -66,6 +66,7 @@ from features.chat.telegram.sdk.telegram_bot_sdk import TelegramBotSDK from features.chat.telegram.telegram_data_resolver import TelegramDataResolver from features.chat.telegram.telegram_domain_mapper import TelegramDomainMapper + from features.chat.url_attachment_resolver import UrlAttachmentResolver from features.chat.whatsapp.sdk.whatsapp_bot_api import WhatsAppBotAPI from features.chat.whatsapp.sdk.whatsapp_bot_sdk import WhatsAppBotSDK from features.chat.whatsapp.whatsapp_data_resolver import WhatsAppDataResolver @@ -898,13 +899,14 @@ def file_uploader( def chat_image_edit_service( self, - attachment_ids: list[str], - operation_guidance: str | None, + attachment_ids: list[str] | None, + urls: list[str] | None = None, + operation_guidance: str | None = None, aspect_ratio: str | None = None, output_size: str | None = None, ) -> "ChatImageEditService": from features.chat.chat_image_edit_service import ChatImageEditService - return ChatImageEditService(attachment_ids, operation_guidance, aspect_ratio, output_size, self) + return ChatImageEditService(attachment_ids, urls, operation_guidance, aspect_ratio, output_size, self) def image_editor( self, @@ -957,13 +959,18 @@ def audio_transcriber( def_extension, audio_content, ) + def url_attachment_resolver(self, url: str) -> "UrlAttachmentResolver": + from features.chat.url_attachment_resolver import UrlAttachmentResolver + return UrlAttachmentResolver(url, self) + def chat_attachment_processor( self, additional_context: str | None, - attachment_ids: list[str], + attachment_ids: list[str] | None, + urls: list[str] | None, ) -> "ChatAttachmentProcessor": from features.chat.chat_attachment_processor import ChatAttachmentProcessor - return ChatAttachmentProcessor(additional_context, attachment_ids, self) + return ChatAttachmentProcessor(additional_context, attachment_ids, urls, self) def dev_announcements_service( self, diff --git a/src/features/chat/chat_attachment_processor.py b/src/features/chat/chat_attachment_processor.py index 0df41a8c..9b71451a 100644 --- a/src/features/chat/chat_attachment_processor.py +++ b/src/features/chat/chat_attachment_processor.py @@ -8,13 +8,12 @@ from db.schema.tools_cache import ToolsCache, ToolsCacheSave from di.di import DI from features.audio.audio_transcriber import AudioTranscriber +from features.chat.chat_attachment_utils import resolve_all_attachments from features.chat.supported_files import KNOWN_AUDIO_FORMATS, KNOWN_DOCS_FORMATS, KNOWN_IMAGE_FORMATS from features.documents.document_search import DocumentSearch from features.external_tools.intelligence_presets import default_tool_for from features.images.computer_vision_analyzer import ComputerVisionAnalyzer from util import log -from util.error_codes import ATTACHMENT_NOT_FOUND, MALFORMED_ATTACHMENT_ID, MISSING_ATTACHMENT_IDS -from util.errors import NotFoundError, ValidationError from util.functions import digest_md5 CACHE_PREFIX = "attachments-analyzer" @@ -39,7 +38,8 @@ class Result(Enum): def __init__( self, additional_context: str | None, - attachment_ids: list[str], + attachment_ids: list[str] | None, + urls: list[str] | None, di: DI, ): self.__attachments = [] @@ -47,21 +47,11 @@ def __init__( self.__errors = [] self.__additional_context = additional_context self.__di = di - self.__validate(attachment_ids) - - def __validate(self, attachment_ids: list[str]) -> None: - log.d(f"Validating {len(attachment_ids)} attachments in chat '{self.__di.invoker_chat_id}'") - if not attachment_ids: - raise ValidationError("Malformed LLM Input Error: No attachment IDs provided. You may retry only once!", MISSING_ATTACHMENT_IDS) # noqa: E501 - attachments: list[ChatMessageAttachment] = [] - for attachment_id in attachment_ids: - if not attachment_id: - raise ValidationError("Malformed LLM Input Error: Attachment ID cannot be empty. You may retry only once!", MALFORMED_ATTACHMENT_ID) # noqa: E501 - attachment_db = self.__di.chat_message_attachment_crud.get(attachment_id) - if not attachment_db: - raise NotFoundError(f"Malformed LLM Input Error: Attachment '{attachment_id}' not found in DB. You may retry only once!", ATTACHMENT_NOT_FOUND) # noqa: E501 - attachments.append(ChatMessageAttachment.model_validate(attachment_db)) - self.__attachments = self.__di.platform_bot_sdk().refresh_attachment_instances(attachments) + log.d( + f"Validating {len(attachment_ids or [])} attachment IDs " + f"and {len(urls or [])} URLs in chat '{self.__di.invoker_chat_id}'", + ) + self.__attachments = resolve_all_attachments(attachment_ids, urls, self.__di) @property def __resolution_status(self) -> Result: diff --git a/src/features/chat/chat_attachment_utils.py b/src/features/chat/chat_attachment_utils.py new file mode 100644 index 00000000..9ae03254 --- /dev/null +++ b/src/features/chat/chat_attachment_utils.py @@ -0,0 +1,30 @@ +from db.schema.chat_message_attachment import ChatMessageAttachment +from di.di import DI +from util.error_codes import ATTACHMENT_NOT_FOUND, MALFORMED_ATTACHMENT_ID, MISSING_ATTACHMENT_IDS +from util.errors import NotFoundError, ValidationError + + +def resolve_all_attachments( + attachment_ids: list[str] | None, + urls: list[str] | None, + di: DI, +) -> list[ChatMessageAttachment]: + if not attachment_ids and not urls: + raise ValidationError("Malformed LLM Input Error: No attachment IDs provided. You may retry only once!", MISSING_ATTACHMENT_IDS) # noqa: E501 + return resolve_local_attachments(attachment_ids or [], di) + resolve_remote_attachments(urls or [], di) + + +def resolve_remote_attachments(urls: list[str], di: DI) -> list[ChatMessageAttachment]: + return [di.url_attachment_resolver(url).execute() for url in urls] + + +def resolve_local_attachments(attachment_ids: list[str], di: DI) -> list[ChatMessageAttachment]: + stale: list[ChatMessageAttachment] = [] + for attachment_id in attachment_ids: + if not attachment_id: + raise ValidationError("Malformed LLM Input Error: Attachment ID cannot be empty. You may retry only once!", MALFORMED_ATTACHMENT_ID) # noqa: E501 + attachment_db = di.chat_message_attachment_crud.get(attachment_id) + if not attachment_db: + raise NotFoundError(f"Malformed LLM Input Error: Attachment '{attachment_id}' not found in DB. You may retry only once!", ATTACHMENT_NOT_FOUND) # noqa: E501 + stale.append(ChatMessageAttachment.model_validate(attachment_db)) + return di.platform_bot_sdk().refresh_attachment_instances(stale) diff --git a/src/features/chat/chat_image_edit_service.py b/src/features/chat/chat_image_edit_service.py index 3dc60758..9ce4c1e8 100644 --- a/src/features/chat/chat_image_edit_service.py +++ b/src/features/chat/chat_image_edit_service.py @@ -2,11 +2,10 @@ from db.schema.chat_message_attachment import ChatMessageAttachment from di.di import DI +from features.chat.chat_attachment_utils import resolve_all_attachments from features.external_tools.intelligence_presets import default_tool_for from features.images.image_editor import ImageEditor from util import log -from util.error_codes import MISSING_IMAGE_ATTACHMENT -from util.errors import ValidationError URLList = list[str | None] ErrorList = list[str | None] @@ -27,16 +26,15 @@ class Result(Enum): def __init__( self, - attachment_ids: list[str], + attachment_ids: list[str] | None, + urls: list[str] | None, operation_guidance: str | None, aspect_ratio: str | None, output_size: str | None, di: DI, ): - if not attachment_ids: - raise ValidationError("No attachment IDs provided", MISSING_IMAGE_ATTACHMENT) self.__di = di - self.__attachments = self.__di.platform_bot_sdk().refresh_attachments_by_ids(attachment_ids) + self.__attachments = resolve_all_attachments(attachment_ids, urls, self.__di) self.__operation_guidance = operation_guidance self.__aspect_ratio = aspect_ratio self.__output_size = output_size diff --git a/src/features/chat/llm_tools/llm_tool_library.py b/src/features/chat/llm_tools/llm_tool_library.py index 2d8624ae..4496dec7 100644 --- a/src/features/chat/llm_tools/llm_tool_library.py +++ b/src/features/chat/llm_tools/llm_tool_library.py @@ -3,6 +3,7 @@ import functools import inspect import json +import re from typing import Any, Callable from langchain_core.language_models import BaseChatModel, LanguageModelInput @@ -39,40 +40,47 @@ ATTACHMENT_OPERATIONS = [KEYWORD_ATTACHMENT_ANALYZE, KEYWORD_ATTACHMENT_IMAGE_EDIT] -def process_attachments( +def process_media( di: DI, - attachment_ids: str, operation: str = KEYWORD_ATTACHMENT_ANALYZE, + attachment_ids: str | None = None, + urls: str | None = None, context: str | None = None, aspect_ratio: str | None = None, size: str | None = None, ) -> str: """ - Processes the contents of the given attachments. Allowed operations are: + Processes the contents of the given media sources (chat attachments and/or external URLs). Allowed operations are: - 'analyze' (default): Analyzes attachments and returns descriptions — images are analyzed using vision (multiple images can be provided and all will be described), audio is transcribed, documents are searched - 'image-edit': Generates a new image using the provided attachments as visual reference or inspiration — use this whenever the partner's images should influence the output (e.g. "use this logo", "generate a variant of this", "apply this style"). Multiple images can be provided for multi-reference generation. To process images individually (one output per image), call this function multiple times with a single image each time. Args: - attachment_ids: [mandatory] A comma-separated list of verbatim, unique 📎 attachment IDs that need to be processed (located in each message); include any dashes, underscores or other symbols; these IDs are not to be cleaned or truncated operation: [mandatory] The action to perform on the attachments + attachment_ids: [optional] A comma-separated list of verbatim, unique 📎 attachment IDs that need to be processed (located in each message); include any dashes, underscores or other symbols; these IDs are not to be cleaned or truncated + urls: [optional] A comma-separated list of external media URLs (starting with http:// or https://) to process; at least one of attachment_ids or urls must be provided context: [optional] Additional task context or guidance, e.g. the user's message/question/caption, if available aspect_ratio: [optional] The desired image's aspect ratio for image editing. Valid options: 1:1, 2:3, 3:2, 3:4, 4:3, 16:9, 9:16, match_input_image. If not explicitly requested, don't send size: [optional] The desired image size/resolution for image editing. Valid options: 1K, 2K, 4K. If not explicitly requested, don't send """ try: operation = operation.lower().strip() - attachment_ids_list = attachment_ids.split(",") + attachment_ids_list = [id.strip() for id in attachment_ids.split(",") if id.strip()] if attachment_ids else [] + raw_urls_list = [url.strip() for url in urls.split(",") if url.strip()] if urls else [] + if operation == KEYWORD_ATTACHMENT_ANALYZE: # Analyze the attachments and convert contents to text descriptions - analyzer = di.chat_attachment_processor(context, attachment_ids_list) + analyzer = di.chat_attachment_processor(context, attachment_ids_list, raw_urls_list or None) result = analyzer.execute() if result == ChatAttachmentProcessor.Result.failed: raise ExternalServiceError("Failed to resolve attachments", EXTERNAL_EMPTY_RESPONSE) return json.dumps({"result": result.value, "attachments": analyzer.result}) elif operation == KEYWORD_ATTACHMENT_IMAGE_EDIT: - log.d(f"LLM requested to process {len(attachment_ids_list)} images in aspect ratio {aspect_ratio}, size {size}") + log.d( + f"LLM requested to process {len(attachment_ids_list)} attachments " + f"and {len(raw_urls_list)} URLs in aspect ratio {aspect_ratio}, size {size}", + ) # Generate images based on the provided context and attachments - result, details = di.chat_image_edit_service(attachment_ids_list, context, aspect_ratio, size).execute() + result, details = di.chat_image_edit_service(attachment_ids_list, raw_urls_list, context, aspect_ratio, size).execute() if result == ChatImageEditService.Result.failed: raise ExternalServiceError("Failed to edit the images! Details: " + str(details), IMAGE_EDIT_FAILED) return __success( @@ -80,7 +88,7 @@ def process_attachments( "status": result.value, "details": details, "description": "Results were already delivered to the partner!", - "next_step": "You may deliver any errors to the partner (but no links or attachments)", + "next_step": "You may deliver any errors to the partner (but no links or 📎 attachment IDs)", }, ) else: @@ -96,7 +104,7 @@ def generate_image( size: str | None = None, ) -> str: """ - Generates (draws) a new image from text only, with no reference images. Use this when creating something entirely new from a description. If the partner has provided image attachments that should influence the output (e.g. "use this logo", "based on this photo"), use process_attachments with 'image-edit' instead. + Generates (draws) a new image from text only, with no reference images. Use this when creating something entirely new from a description. If the partner has provided image attachments that should influence the output (e.g. "use this logo", "based on this photo"), use process_media with 'image-edit' instead. Args: prompt: [mandatory] The user's description or prompt for the generated image @@ -122,19 +130,29 @@ def generate_image( return __error(e) -def fetch_web_content(di: DI, url: str) -> str: +def fetch_web_content(di: DI, url: str, offset: str | None = None) -> str: """ Fetches the text content from the given web page URL, including Twitter / X posts. + For long pages, use the 'offset' parameter to paginate through the content. Args: url: [mandatory] A valid URL of the web page, starting with 'http://' or 'https://' provided in the text + offset: [optional] Character offset to start reading from (for paginating long content); returned in previous responses as 'next_offset' """ try: fetcher = di.web_fetcher(url, auto_fetch_html = True) html = str(fetcher.html) text = di.html_content_cleaner(html).clean_up() - result = text[:TOOL_TRUNCATE_LENGTH] + "..." if len(text) > TOOL_TRUNCATE_LENGTH else text - return __success({"content": result}) + start = int(offset) if offset else 0 + chunk = text[start:start + TOOL_TRUNCATE_LENGTH] + has_more = start + TOOL_TRUNCATE_LENGTH < len(text) + response: dict[str, Any] = {"content": chunk} + if has_more: + response["next_offset"] = str(start + TOOL_TRUNCATE_LENGTH) + response["total_length"] = len(text) + if re.search(r"!\[.*?]\(https?://", text): + response["next_step"] = "Media URLs found on this page can be passed to other tools that accept URLs for further processing" + return __success(response) except Exception as e: return __error(e) @@ -520,7 +538,7 @@ def __error(message: str | Exception) -> str: ALL_LLM_TOOLS: dict[str, Callable[..., str]] = { "fetch_web_content": fetch_web_content, - "process_attachments": process_attachments, + "process_media": process_media, "get_exchange_rate": get_exchange_rate, "set_up_currency_price_alert": set_up_currency_price_alert, "remove_currency_price_alerts": remove_currency_price_alerts, diff --git a/src/features/chat/url_attachment_resolver.py b/src/features/chat/url_attachment_resolver.py new file mode 100644 index 00000000..dbd34c77 --- /dev/null +++ b/src/features/chat/url_attachment_resolver.py @@ -0,0 +1,67 @@ +import urllib.parse +from uuid import UUID + +import requests + +from db.schema.chat_message_attachment import ChatMessageAttachment +from di.di import DI +from features.chat.supported_files import KNOWN_FILE_FORMATS +from util.config import config +from util.error_codes import UNSUPPORTED_MEDIA_TYPE +from util.errors import ValidationError +from util.functions import digest_md5 + + +class UrlAttachmentResolver: + + __url: str + __chat_id: UUID + + def __init__(self, url: str, di: DI): + self.__url = url + self.__chat_id = UUID(di.invoker_chat_id) + + def execute(self) -> ChatMessageAttachment: + mime_type: str | None = self.__mime_from_head() or self.__mime_from_extension() + if not mime_type: + raise ValidationError( + f"Cannot determine a supported media type for URL: {self.__url}", + UNSUPPORTED_MEDIA_TYPE, + ) + ext = self.__extension_for(mime_type) + attachment_id = f"url-{digest_md5(self.__url)}" + return ChatMessageAttachment( + id = attachment_id, + chat_id = self.__chat_id, + message_id = f"virtual-{attachment_id}", + last_url = self.__url, + mime_type = mime_type, + extension = ext, + ) + + def __mime_from_head(self) -> str | None: + try: + response = requests.head(self.__url, timeout = config.web_timeout_s, allow_redirects = True) + content_type = response.headers.get("Content-Type", "") + if content_type: + candidate = content_type.split(";")[0].strip() + if candidate in KNOWN_FILE_FORMATS.values(): + return candidate + except Exception: + pass + return None + + def __mime_from_extension(self) -> str | None: + path = urllib.parse.urlparse(self.__url).path + if "." in path: + ext = path.rsplit(".", 1)[-1].lower() + return KNOWN_FILE_FORMATS.get(ext) + return None + + def __extension_for(self, mime_type: str) -> str | None: + path = urllib.parse.urlparse(self.__url).path + if "." in path: + ext = path.rsplit(".", 1)[-1].lower() + if ext in KNOWN_FILE_FORMATS: + return ext + return next((k for k, v in KNOWN_FILE_FORMATS.items() if v == mime_type), None) diff --git a/src/features/prompting/prompt_library.py b/src/features/prompting/prompt_library.py index fe4e0faf..bd8d7cc7 100644 --- a/src/features/prompting/prompt_library.py +++ b/src/features/prompting/prompt_library.py @@ -43,6 +43,11 @@ class _ContextLibrary: "Attachment IDs are machine-generated, so the user's have no use or understanding of them. " "YOU MUST NEVER SEND ATTACHMENT IDS TO THE USER, IN ANY WAY, SHAPE OR FORM. " "When required, analyze and use the message attachment functions to provide more relevant responses and replies. " + "When the contents of a processed attachment contain a question, request, or task directed at you, " + "respond to it directly — do not merely describe or summarize the attachment. " + "When fetching web content, always paginate through all available pages before responding — " + "if a response contains 'next_offset', call the function again with that offset until all content is retrieved. " + "Media URLs are typically found at the end of the content and can be used with other tools for further processing. " "You should run functions again if that's what your chat partner asks for, even if you've just run them. " "Never assume that you have processed attachments because a past message in the chat has claimed that. " ).strip(), diff --git a/src/features/web_browsing/html_content_cleaner.py b/src/features/web_browsing/html_content_cleaner.py index 68168e1a..48880a86 100644 --- a/src/features/web_browsing/html_content_cleaner.py +++ b/src/features/web_browsing/html_content_cleaner.py @@ -41,6 +41,9 @@ def clean_up(self) -> str: log.t(f"Cache expired for '{cache_key}'") log.t(f"Cache miss for '{cache_key}'") + # extract image URLs from raw HTML before readabilipy strips them + image_markdowns = self.__extract_images(self.raw_html) + content_json = simple_json_from_html_string(self.raw_html) log.t(f"Processed HTML, received {len(content_json)} content items") # replace HTML markers with Markdown markers @@ -52,13 +55,17 @@ def clean_up(self) -> str: self.plain_text = re.sub(r"
(.*?)
", r"\n###### \1\n", self.plain_text) self.plain_text = re.sub(r"]*?\s+)?href=\"([^\"]*)\"[^>]*>(.*?)", r"[\2](\1)", self.plain_text) # clean other tags - self.plain_text = self._remove_menus(self.plain_text) # navigation components + self.plain_text = self.__remove_menus(self.plain_text) # navigation components self.plain_text = re.sub(r"
  • (.*?)
  • ", r"\n- \1\n", self.plain_text) # list items self.plain_text = re.sub(r"<[ou]l>\s*", "", self.plain_text) # empty lists self.plain_text = re.sub(r"<[^>]+>", " ", self.plain_text) # remove remaining tags # remove extra whitespace self.plain_text = re.sub(r"\n\s*\n+", "\n", self.plain_text) # empty newlines self.plain_text = re.sub(r"[ \t]+", " ", self.plain_text).strip() # horizontal spaces + # append image links extracted from raw HTML + if image_markdowns: + self.plain_text += "\n" + "\n".join(image_markdowns) + # finally cache the cleaned content for future use self.__di.tools_cache_crud.save( ToolsCacheSave( key = cache_key, @@ -70,7 +77,53 @@ def clean_up(self) -> str: return self.plain_text @staticmethod - def _remove_menus(html): + def __extract_images(html: str) -> list[str]: + results: list[str] = [] + seen_base_urls: set[str] = set() + for match in re.finditer(r"]*?src=[\"']([^\"']+)[\"'][^>]*?>", html, re.IGNORECASE): + tag = match.group(0) + src = match.group(1) + if HTMLContentCleaner.__is_noise_image(tag, src): + continue + base_url = src.split("?")[0] + if base_url in seen_base_urls: + continue + seen_base_urls.add(base_url) + alt_match = re.search(r"alt=[\"']([^\"']*)[\"']", tag, re.IGNORECASE) + alt = alt_match.group(1) if alt_match and alt_match.group(1) else "image" + results.append(f"![{alt}]({src})") + return results + + @staticmethod + def __is_noise_image(tag: str, src: str) -> bool: + # tracking pixels and tiny icons + width_match = re.search(r"width=[\"']?(\d+)", tag, re.IGNORECASE) + height_match = re.search(r"height=[\"']?(\d+)", tag, re.IGNORECASE) + if width_match and height_match: + w, h = int(width_match.group(1)), int(height_match.group(1)) + if w <= 3 or h <= 3: + return True + # decorative images + if re.search(r"role=[\"']presentation[\"']", tag, re.IGNORECASE): + return True + # data URIs (inline base64 blobs) + if src.startswith("data:"): + return True + # analytics and tracking domains + noise_domains = [ + "google-analytics.com", "googletagmanager.com", "facebook.com/tr", + "doubleclick.net", "adservice.google", "analytics.", "pixel.", + "bat.bing.com", "tr.snapchat.com", "ads.linkedin.com", + ] + src_lower = src.lower() + if any(d in src_lower for d in noise_domains): + return True + # common noise filename patterns + noise_patterns = ["spacer", "pixel", "tracking", "beacon", "blank.gif", "1x1"] + return any(p in src_lower for p in noise_patterns) + + @staticmethod + def __remove_menus(html): patterns = [ r"]*>.*?", r"]*>.*?", diff --git a/src/util/error_codes.py b/src/util/error_codes.py index 7fc1936f..58480c98 100644 --- a/src/util/error_codes.py +++ b/src/util/error_codes.py @@ -35,6 +35,7 @@ SPONSORED_USER_TRANSFER_NOT_ALLOWED = 1035 TOO_MANY_INPUT_IMAGES = 1037 EMPTY_CHAT_SETTINGS_PAYLOAD = 1038 +UNSUPPORTED_MEDIA_TYPE = 1039 # Not found errors (2000-2999) USER_NOT_FOUND = 2001 diff --git a/test/features/chat/test_chat_attachment_processor.py b/test/features/chat/test_chat_attachment_processor.py index de0f3ab4..8fee9bdf 100644 --- a/test/features/chat/test_chat_attachment_processor.py +++ b/test/features/chat/test_chat_attachment_processor.py @@ -14,6 +14,7 @@ from db.schema.user import User from features.chat.chat_attachment_processor import CACHE_TTL, ChatAttachmentProcessor from features.chat.telegram.sdk.telegram_bot_sdk import TelegramBotSDK +from features.chat.url_attachment_resolver import UrlAttachmentResolver from features.integrations.platform_bot_sdk import PlatformBotSDK from util.config import config from util.errors import NotFoundError, ValidationError @@ -93,9 +94,10 @@ def setUp(self): self.mock_cache_crud.create_key.return_value = "test_cache_key" self.mock_di.require_invoker_chat_type = MagicMock(return_value = ChatConfigDB.ChatType.telegram) - # Use a real SDK instance so the resolver under test returns real models + # Use real SDK/resolver instances so the code under test returns real models self.mock_di.telegram_bot_sdk = TelegramBotSDK(self.mock_di) self.mock_di.platform_bot_sdk = MagicMock(return_value = PlatformBotSDK(self.mock_di)) + self.mock_di.url_attachment_resolver.side_effect = lambda url: UrlAttachmentResolver(url, self.mock_di) @requests_mock.Mocker() def test_execute_with_cache_hit(self, m: requests_mock.Mocker): @@ -109,6 +111,7 @@ def test_execute_with_cache_hit(self, m: requests_mock.Mocker): resolver = ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["1"], + urls = None, di = self.mock_di, ) result = resolver.execute() @@ -134,6 +137,7 @@ def test_execute_with_cache_miss(self, m: requests_mock.Mocker): resolver = ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["1"], + urls = None, di = self.mock_di, ) result = resolver.execute() @@ -149,6 +153,7 @@ def test_empty_attachment_ids_list(self): ChatAttachmentProcessor( additional_context = "context", attachment_ids = [], + urls = None, di = self.mock_di, ) self.assertIn("No attachment IDs provided", str(context.exception)) @@ -158,6 +163,7 @@ def test_empty_attachment_id_string(self): ChatAttachmentProcessor( additional_context = "context", attachment_ids = [""], + urls = None, di = self.mock_di, ) self.assertIn("Attachment ID cannot be empty", str(context.exception)) @@ -169,6 +175,7 @@ def test_attachment_not_found_in_db(self): ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["nonexistent"], + urls = None, di = self.mock_di, ) self.assertIn("not found in DB", str(context.exception)) @@ -193,6 +200,7 @@ def test_fetch_text_content_with_audio(self, m: requests_mock.Mocker): resolver = ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["2"], + urls = None, di = self.mock_di, ) content = resolver.fetch_text_content(audio_attachment) @@ -220,6 +228,7 @@ def test_fetch_text_content_with_pdf_document(self, m: requests_mock.Mocker): resolver = ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["4"], + urls = None, di = self.mock_di, ) content = resolver.fetch_text_content(pdf_attachment) @@ -243,7 +252,69 @@ def test_fetch_text_content_with_unsupported_type(self, m: requests_mock.Mocker) resolver = ChatAttachmentProcessor( additional_context = "context", attachment_ids = ["3"], + urls = None, di = self.mock_di, ) content = resolver.fetch_text_content(unsupported_attachment) self.assertIsNone(content) + + @requests_mock.Mocker() + def test_url_resolved_attachment_skips_db_lookup(self, m: requests_mock.Mocker): + virtual_url = "https://example.com/image.png" + m.head(virtual_url, exc = ConnectionError("timeout")) + m.get(virtual_url, content = b"image data", status_code = 200) + self.mock_cache_crud.get.return_value = self.cache_entry.model_dump() + + mock_cv_instance = MagicMock() + mock_cv_instance.execute.return_value = self.cached_content + self.mock_di.computer_vision_analyzer.return_value = mock_cv_instance + + resolver = ChatAttachmentProcessor( + additional_context = "context", + attachment_ids = [], + di = self.mock_di, + urls = [virtual_url], + ) + result = resolver.execute() + + self.assertEqual(result, ChatAttachmentProcessor.Result.success) + self.assertEqual(len(resolver.result), 1) + self.assertTrue(resolver.result[0]["id"].startswith("url-")) + # DB lookup must not be called for URL-resolved attachments + self.mock_chat_message_attachment_crud.get.assert_not_called() + + @requests_mock.Mocker() + def test_url_resolved_merged_with_db_attachments(self, m: requests_mock.Mocker): + virtual_url = "https://example.com/image.png" + m.head(virtual_url, exc = ConnectionError("timeout")) + m.get(virtual_url, content = b"image data", status_code = 200) + m.get(str(self.attachment.last_url), content = b"image data", status_code = 200) + self.mock_cache_crud.get.return_value = self.cache_entry.model_dump() + + mock_cv_instance = MagicMock() + mock_cv_instance.execute.return_value = self.cached_content + self.mock_di.computer_vision_analyzer.return_value = mock_cv_instance + + resolver = ChatAttachmentProcessor( + additional_context = "context", + attachment_ids = ["1"], + di = self.mock_di, + urls = [virtual_url], + ) + result = resolver.execute() + + self.assertEqual(result, ChatAttachmentProcessor.Result.success) + self.assertEqual(len(resolver.result), 2) + result_ids = {r["id"] for r in resolver.result} + self.assertTrue(any(rid.startswith("url-") for rid in result_ids)) + self.assertIn("1", result_ids) + + def test_empty_ids_and_no_urls_raises_error(self): + with self.assertRaises(ValidationError) as context: + ChatAttachmentProcessor( + additional_context = "context", + attachment_ids = [], + urls = None, + di = self.mock_di, + ) + self.assertIn("No attachment IDs provided", str(context.exception)) diff --git a/test/features/chat/test_chat_attachment_utils.py b/test/features/chat/test_chat_attachment_utils.py new file mode 100644 index 00000000..2b294706 --- /dev/null +++ b/test/features/chat/test_chat_attachment_utils.py @@ -0,0 +1,137 @@ +import unittest +from datetime import datetime, timedelta +from unittest.mock import MagicMock +from uuid import UUID + +from db.model.chat_message_attachment import ChatMessageAttachmentDB +from db.schema.chat_message_attachment import ChatMessageAttachment +from features.chat.chat_attachment_utils import resolve_all_attachments, resolve_local_attachments +from util.errors import NotFoundError, ValidationError + + +class ValidateSourcesTest(unittest.TestCase): + + def setUp(self): + self.mock_di = MagicMock() + self.mock_di.platform_bot_sdk = MagicMock(return_value = MagicMock()) + mock_resolver = MagicMock() + mock_resolver.execute.return_value = MagicMock() + self.mock_di.url_attachment_resolver.return_value = mock_resolver + + def test_both_empty_raises(self): + with self.assertRaises(ValidationError) as ctx: + resolve_all_attachments([], [], self.mock_di) + self.assertIn("No attachment IDs provided", str(ctx.exception)) + + def test_both_none_raises(self): + with self.assertRaises(ValidationError) as ctx: + resolve_all_attachments(None, None, self.mock_di) + self.assertIn("No attachment IDs provided", str(ctx.exception)) + + +class FetchAttachmentsTest(unittest.TestCase): + + def setUp(self): + self.mock_di = MagicMock() + self.attachment_db = ChatMessageAttachmentDB( + id = "att1", + external_id = "ext1", + chat_id = UUID(int = 1), + message_id = "msg1", + size = 1024, + last_url = "http://test.com/image.png", + last_url_until = int((datetime.now() + timedelta(hours = 1)).timestamp()), + extension = "png", + mime_type = "image/png", + ) + self.mock_di.chat_message_attachment_crud.get.return_value = self.attachment_db + self.mock_di.platform_bot_sdk.return_value.refresh_attachment_instances.side_effect = lambda x: x + + def test_empty_ids_returns_empty(self): + result = resolve_local_attachments([], self.mock_di) + self.assertEqual(result, []) + self.mock_di.chat_message_attachment_crud.get.assert_not_called() + + def test_valid_id_returns_attachment(self): + result = resolve_local_attachments(["att1"], self.mock_di) + self.assertEqual(len(result), 1) + self.assertEqual(result[0].id, "att1") + self.assertIsInstance(result[0], ChatMessageAttachment) + + def test_empty_string_id_raises(self): + with self.assertRaises(ValidationError) as ctx: + resolve_local_attachments([""], self.mock_di) + self.assertIn("Attachment ID cannot be empty", str(ctx.exception)) + + def test_not_found_raises(self): + self.mock_di.chat_message_attachment_crud.get.return_value = None + with self.assertRaises(NotFoundError) as ctx: + resolve_local_attachments(["missing"], self.mock_di) + self.assertIn("not found in DB", str(ctx.exception)) + + def test_multiple_ids_fetches_all(self): + result = resolve_local_attachments(["att1", "att1"], self.mock_di) + self.assertEqual(len(result), 2) + + +class ResolveAllAttachmentsTest(unittest.TestCase): + + def setUp(self): + self.mock_di = MagicMock() + self.attachment_db = ChatMessageAttachmentDB( + id = "att1", + external_id = "ext1", + chat_id = UUID(int = 1), + message_id = "msg1", + size = 1024, + last_url = "http://test.com/image.png", + last_url_until = int((datetime.now() + timedelta(hours = 1)).timestamp()), + extension = "png", + mime_type = "image/png", + ) + self.attachment = ChatMessageAttachment.model_validate(self.attachment_db) + self.mock_di.chat_message_attachment_crud.get.return_value = self.attachment_db + + self.mock_platform_sdk = MagicMock() + self.mock_di.platform_bot_sdk = MagicMock(return_value = self.mock_platform_sdk) + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] + + self.url_attachment = ChatMessageAttachment( + id = "url-abc123", + chat_id = UUID(int = 1), + message_id = "virtual", + mime_type = "image/png", + extension = "png", + last_url = "http://example.com/photo.png", + last_url_until = int((datetime.now() + timedelta(hours = 1)).timestamp()), + ) + mock_resolver = MagicMock() + mock_resolver.execute.return_value = self.url_attachment + self.mock_di.url_attachment_resolver.return_value = mock_resolver + + def test_both_empty_raises(self): + with self.assertRaises(ValidationError): + resolve_all_attachments([], [], self.mock_di) + + def test_ids_only(self): + result = resolve_all_attachments(["att1"], [], self.mock_di) + self.assertEqual(result, [self.attachment]) + self.mock_platform_sdk.refresh_attachment_instances.assert_called_once() + self.mock_di.url_attachment_resolver.assert_not_called() + + def test_urls_only(self): + self.mock_platform_sdk.refresh_attachment_instances.return_value = [] + result = resolve_all_attachments([], ["http://example.com/photo.png"], self.mock_di) + self.assertEqual(len(result), 1) + self.assertEqual(result[0], self.url_attachment) + + def test_ids_and_urls_merged(self): + result = resolve_all_attachments(["att1"], ["http://example.com/photo.png"], self.mock_di) + self.assertEqual(len(result), 2) + self.assertIn(self.attachment, result) + self.assertIn(self.url_attachment, result) + + def test_refresh_called_with_empty_when_no_ids(self): + self.mock_platform_sdk.refresh_attachment_instances.return_value = [] + resolve_all_attachments([], ["http://example.com/photo.png"], self.mock_di) + self.mock_platform_sdk.refresh_attachment_instances.assert_called_once_with([]) diff --git a/test/features/chat/test_chat_image_edit_service.py b/test/features/chat/test_chat_image_edit_service.py index da798f36..29f2d6e4 100644 --- a/test/features/chat/test_chat_image_edit_service.py +++ b/test/features/chat/test_chat_image_edit_service.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch from uuid import UUID +import requests_mock from pydantic import SecretStr from db.model.chat_config import ChatConfigDB @@ -11,11 +12,14 @@ from db.schema.chat_message_attachment import ChatMessageAttachment from db.schema.user import User from features.chat.chat_image_edit_service import ChatImageEditService +from features.chat.url_attachment_resolver import UrlAttachmentResolver +from util.config import config class ChatImageEditServiceTest(unittest.TestCase): def setUp(self): + config.web_timeout_s = 1 self.attachment_ids = ["attachment1", "attachment2"] self.operation_guidance = None self.mock_di = MagicMock() @@ -87,7 +91,9 @@ def setUp(self): ) self.attachment_no_url = ChatMessageAttachment.model_validate(self.mock_attachment_no_url_db) - self.patcher = patch.object(self.mock_platform_sdk, "refresh_attachments_by_ids") + self.mock_di.url_attachment_resolver.side_effect = lambda url: UrlAttachmentResolver(url, self.mock_di) + + self.patcher = patch.object(self.mock_platform_sdk, "refresh_attachment_instances") self.mock_refresh = self.patcher.start() self.mock_refresh.return_value = [self.attachment] @@ -95,9 +101,10 @@ def tearDown(self): self.patcher.stop() def test_init_success(self): - with patch.object(self.mock_platform_sdk, "refresh_attachments_by_ids", return_value = [self.attachment]): + with patch.object(self.mock_platform_sdk, "refresh_attachment_instances", return_value = [self.attachment]): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -106,7 +113,7 @@ def test_init_success(self): self.assertIsInstance(service, ChatImageEditService) def test_execute_edit_image_success_single(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] mock_editor = MagicMock() mock_editor.execute.return_value = "http://test.com/edited_image.png" mock_editor.error = None @@ -114,6 +121,7 @@ def test_execute_edit_image_success_single(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -139,7 +147,7 @@ def test_execute_edit_image_success_single(self): ) def test_execute_edit_image_success_multi(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment, self.attachment2] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment, self.attachment2] mock_editor = MagicMock() mock_editor.execute.return_value = "http://test.com/edited_image.png" mock_editor.error = None @@ -147,6 +155,7 @@ def test_execute_edit_image_success_multi(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -166,7 +175,7 @@ def test_execute_edit_image_success_multi(self): ) def test_execute_edit_image_partial_some_urls_missing(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment, self.attachment_no_url] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment, self.attachment_no_url] mock_editor = MagicMock() mock_editor.execute.return_value = "http://test.com/edited_image.png" mock_editor.error = None @@ -174,6 +183,7 @@ def test_execute_edit_image_partial_some_urls_missing(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -194,10 +204,11 @@ def test_execute_edit_image_partial_some_urls_missing(self): ) def test_execute_edit_image_all_urls_missing(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment_no_url] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment_no_url] service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -210,7 +221,7 @@ def test_execute_edit_image_all_urls_missing(self): self.mock_di.image_editor.assert_not_called() def test_execute_edit_image_failed_editor_error(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] mock_editor = MagicMock() mock_editor.execute.return_value = None mock_editor.error = "Image editing failed" @@ -218,6 +229,7 @@ def test_execute_edit_image_failed_editor_error(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -231,7 +243,7 @@ def test_execute_edit_image_failed_editor_error(self): self.mock_platform_sdk.smart_send_photo.assert_not_called() def test_execute_edit_image_failed_no_result(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] mock_editor = MagicMock() mock_editor.execute.return_value = None mock_editor.error = None @@ -239,6 +251,7 @@ def test_execute_edit_image_failed_no_result(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -251,7 +264,7 @@ def test_execute_edit_image_failed_no_result(self): self.mock_platform_sdk.smart_send_photo.assert_not_called() def test_execute_edit_image_exception(self): - self.mock_platform_sdk.refresh_attachments_by_ids.return_value = [self.attachment] + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] mock_editor = MagicMock() mock_editor.execute.side_effect = Exception("Test exception") mock_editor.error = None @@ -259,6 +272,7 @@ def test_execute_edit_image_exception(self): service = ChatImageEditService( attachment_ids = self.attachment_ids, + urls = None, operation_guidance = self.operation_guidance, aspect_ratio = None, output_size = None, @@ -269,3 +283,66 @@ def test_execute_edit_image_exception(self): self.assertEqual(result, ChatImageEditService.Result.failed) self.assertEqual(details, [{"url": None, "error": "Test exception"}]) self.mock_platform_sdk.smart_send_photo.assert_not_called() + + @requests_mock.Mocker() + def test_url_resolved_attachment_skips_refresh(self, m: requests_mock.Mocker): + virtual_url = "https://example.com/image.png" + m.head(virtual_url, exc = ConnectionError("timeout")) + self.mock_platform_sdk.refresh_attachment_instances.return_value = [] + mock_editor = MagicMock() + mock_editor.execute.return_value = "http://test.com/edited.png" + mock_editor.error = None + self.mock_di.image_editor.return_value = mock_editor + self.mock_di.invoker_chat_id = "00000000000000000000000000000001" + + service = ChatImageEditService( + attachment_ids = [], + urls = [virtual_url], + operation_guidance = None, + aspect_ratio = None, + output_size = None, + di = self.mock_di, + ) + result, details = service.execute() + + self.assertEqual(result, ChatImageEditService.Result.success) + self.mock_platform_sdk.refresh_attachment_instances.assert_called_once_with([]) + call_kwargs = self.mock_di.image_editor.call_args[1] + self.assertEqual(call_kwargs["image_urls"], [virtual_url]) + self.assertEqual(call_kwargs["input_mime_types"], ["image/png"]) + + @requests_mock.Mocker() + def test_url_resolved_merged_with_db_attachments(self, m: requests_mock.Mocker): + virtual_url = "https://example.com/virtual.png" + m.head(virtual_url, exc = ConnectionError("timeout")) + self.mock_platform_sdk.refresh_attachment_instances.return_value = [self.attachment] + mock_editor = MagicMock() + mock_editor.execute.return_value = "http://test.com/edited.png" + mock_editor.error = None + self.mock_di.image_editor.return_value = mock_editor + self.mock_di.invoker_chat_id = "00000000000000000000000000000001" + + service = ChatImageEditService( + attachment_ids = ["attachment1"], + urls = [virtual_url], + operation_guidance = None, + aspect_ratio = None, + output_size = None, + di = self.mock_di, + ) + service.execute() + + call_kwargs = self.mock_di.image_editor.call_args[1] + self.assertIn(virtual_url, call_kwargs["image_urls"]) + self.assertIn("http://test.com/image.png", call_kwargs["image_urls"]) + + def test_empty_ids_and_no_urls_raises_error(self): + with self.assertRaises(Exception): + ChatImageEditService( + attachment_ids = [], + urls = None, + operation_guidance = None, + aspect_ratio = None, + output_size = None, + di = self.mock_di, + ) diff --git a/test/features/chat/test_url_attachment_resolver.py b/test/features/chat/test_url_attachment_resolver.py new file mode 100644 index 00000000..d9d8a506 --- /dev/null +++ b/test/features/chat/test_url_attachment_resolver.py @@ -0,0 +1,94 @@ +import unittest +from unittest.mock import MagicMock +from uuid import UUID + +import requests_mock + +from features.chat.url_attachment_resolver import UrlAttachmentResolver +from util.config import config +from util.errors import ValidationError + +CHAT_ID = UUID(int = 1) +IMAGE_URL = "https://example.com/photo.jpg" +PNG_URL = "https://example.com/image.png" +UNSUPPORTED_URL = "https://example.com/file.xyz" +NO_EXT_URL = "https://example.com/media" + + +class UrlAttachmentResolverTest(unittest.TestCase): + + def setUp(self): + config.web_retries = 1 + config.web_retry_delay_s = 0 + config.web_timeout_s = 1 + self.mock_di = MagicMock() + self.mock_di.invoker_chat_id = CHAT_ID.hex + + def _resolver(self, url: str) -> UrlAttachmentResolver: + return UrlAttachmentResolver(url, self.mock_di) + + @requests_mock.Mocker() + def test_mime_from_head(self, m: requests_mock.Mocker): + m.head(IMAGE_URL, headers = {"Content-Type": "image/jpeg"}, status_code = 200) + result = self._resolver(IMAGE_URL).execute() + self.assertEqual(result.mime_type, "image/jpeg") + self.assertEqual(result.last_url, IMAGE_URL) + self.assertTrue(result.id.startswith("url-")) + self.assertEqual(result.chat_id, CHAT_ID) + + @requests_mock.Mocker() + def test_message_id_uses_virtual_prefix(self, m: requests_mock.Mocker): + m.head(PNG_URL, exc = ConnectionError("timeout")) + result = self._resolver(PNG_URL).execute() + self.assertEqual(result.message_id, f"virtual-{result.id}") + + @requests_mock.Mocker() + def test_mime_from_extension_fallback_when_head_fails(self, m: requests_mock.Mocker): + m.head(PNG_URL, exc = ConnectionError("timeout")) + result = self._resolver(PNG_URL).execute() + self.assertEqual(result.mime_type, "image/png") + self.assertEqual(result.extension, "png") + + @requests_mock.Mocker() + def test_mime_from_extension_fallback_when_head_returns_no_content_type(self, m: requests_mock.Mocker): + m.head(PNG_URL, headers = {}, status_code = 200) + result = self._resolver(PNG_URL).execute() + self.assertEqual(result.mime_type, "image/png") + + @requests_mock.Mocker() + def test_mime_from_extension_fallback_when_head_returns_unsupported_content_type(self, m: requests_mock.Mocker): + m.head(PNG_URL, headers = {"Content-Type": "application/octet-stream"}, status_code = 200) + result = self._resolver(PNG_URL).execute() + self.assertEqual(result.mime_type, "image/png") + + @requests_mock.Mocker() + def test_unsupported_type_rejection(self, m: requests_mock.Mocker): + m.head(UNSUPPORTED_URL, headers = {}, status_code = 200) + with self.assertRaises(ValidationError): + self._resolver(UNSUPPORTED_URL).execute() + + @requests_mock.Mocker() + def test_no_extension_and_head_fails_raises_validation_error(self, m: requests_mock.Mocker): + m.head(NO_EXT_URL, exc = ConnectionError("refused")) + with self.assertRaises(ValidationError): + self._resolver(NO_EXT_URL).execute() + + @requests_mock.Mocker() + def test_head_content_type_with_charset_params_stripped(self, m: requests_mock.Mocker): + m.head(PNG_URL, headers = {"Content-Type": "image/png; charset=utf-8"}, status_code = 200) + result = self._resolver(PNG_URL).execute() + self.assertEqual(result.mime_type, "image/png") + + @requests_mock.Mocker() + def test_url_with_query_params_extension_parsed_correctly(self, m: requests_mock.Mocker): + url_with_query = "https://example.com/photo.jpg?token=abc&size=large" + m.head(url_with_query, exc = ConnectionError("timeout")) + result = self._resolver(url_with_query).execute() + self.assertEqual(result.mime_type, "image/jpeg") + + @requests_mock.Mocker() + def test_deterministic_id_from_same_url(self, m: requests_mock.Mocker): + m.head(PNG_URL, exc = ConnectionError("timeout")) + result1 = self._resolver(PNG_URL).execute() + result2 = self._resolver(PNG_URL).execute() + self.assertEqual(result1.id, result2.id) diff --git a/test/features/web_browsing/test_html_content_cleaner.py b/test/features/web_browsing/test_html_content_cleaner.py index 30e493ea..791ab03c 100644 --- a/test/features/web_browsing/test_html_content_cleaner.py +++ b/test/features/web_browsing/test_html_content_cleaner.py @@ -69,9 +69,44 @@ def test_clean_up_markup_conversion(self): self.assertIn("### Header3", result) self.assertIn("Link", result) # Links get removed by the Readability lib + def test_clean_up_preserves_image_urls(self): + html = ( + "

    Text before

    " + 'A photo' + '' + "

    Text after

    " + ) + self.mock_cache_crud.get.return_value = None + cleaner = HTMLContentCleaner(html, self.mock_di) + result = cleaner.clean_up() + self.assertIn("![A photo](https://example.com/photo.png)", result) + self.assertIn("![image](https://example.com/logo.png)", result) + + def test_clean_up_filters_noise_images(self): + html = ( + "

    Content

    " + 'Real photo' + '' + '' + 'inline' + '' + '' + "

    End

    " + ) + self.mock_cache_crud.get.return_value = None + cleaner = HTMLContentCleaner(html, self.mock_di) + result = cleaner.clean_up() + self.assertIn("![Real photo](https://example.com/real.jpg)", result) + self.assertNotIn("pixel.gif", result) + self.assertNotIn("spacer", result) + self.assertNotIn("data:image", result) + self.assertNotIn("decorative", result) + self.assertNotIn("beacon", result) + def test_remove_navigational_elements(self): html = "
    Header
    Menu
    Menu div
    " - content = HTMLContentCleaner._remove_menus(html) + # noinspection PyUnresolvedReferences + content = HTMLContentCleaner._HTMLContentCleaner__remove_menus(html) self.assertNotIn("Navigation", content) self.assertNotIn("Header", content) self.assertNotIn("Menu", content) From 724dfb050268c75137ae33bdabea7e3626b99ddf Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:45:13 +0200 Subject: [PATCH 04/10] Update open specs --- .../.openspec.yaml | 2 + .../design.md | 76 +++++++++++++++++++ .../proposal.md | 32 ++++++++ .../external-url-media-resolution/spec.md | 61 +++++++++++++++ .../tasks.md | 22 ++++++ 5 files changed, 193 insertions(+) create mode 100644 openspec/changes/archive/2026-05-16-add-external-media-urls/.openspec.yaml create mode 100644 openspec/changes/archive/2026-05-16-add-external-media-urls/design.md create mode 100644 openspec/changes/archive/2026-05-16-add-external-media-urls/proposal.md create mode 100644 openspec/changes/archive/2026-05-16-add-external-media-urls/specs/external-url-media-resolution/spec.md create mode 100644 openspec/changes/archive/2026-05-16-add-external-media-urls/tasks.md diff --git a/openspec/changes/archive/2026-05-16-add-external-media-urls/.openspec.yaml b/openspec/changes/archive/2026-05-16-add-external-media-urls/.openspec.yaml new file mode 100644 index 00000000..81cd71fe --- /dev/null +++ b/openspec/changes/archive/2026-05-16-add-external-media-urls/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-11 diff --git a/openspec/changes/archive/2026-05-16-add-external-media-urls/design.md b/openspec/changes/archive/2026-05-16-add-external-media-urls/design.md new file mode 100644 index 00000000..9b557f3a --- /dev/null +++ b/openspec/changes/archive/2026-05-16-add-external-media-urls/design.md @@ -0,0 +1,76 @@ +## Context + +The LLM tool `process_attachments` currently only accepts attachment IDs — references to files stored in the DB after being received from Telegram/WhatsApp. The downstream processors (`ChatAttachmentProcessor`, `ChatImageEditService`) work with `ChatMessageAttachment` objects that carry a URL, MIME type, and extension. The attachment URLs contain bot tokens in their paths and must never be exposed to the LLM or logged. + +External URLs provided by users are public and don't have this sensitivity. They need to enter the same processing pipeline without going through DB storage or platform SDK refresh. + +## Goals / Non-Goals + +**Goals:** +- Allow the LLM to process external URLs through the same media pipeline as chat attachments +- Support both `analyze` and `image-edit` operations for URL-sourced media +- Keep the change minimal — reuse existing processors, don't fork the pipeline +- Cache URL-based analysis results using URL hash as cache key + +**Non-Goals:** +- No DB persistence of URL-sourced media +- No authentication/cookie support for fetching URLs behind login walls +- No new file format support — URLs must resolve to already-supported formats +- No changes to the attachment ingestion pipeline from platforms + +## Decisions + +### 1. Separate `urls` parameter instead of mixed-format input + +Add `urls: str | None` as a new comma-separated parameter alongside existing `attachment_ids`. Do not merge them into a single field. + +**Why:** LLMs (especially weaker ones) reliably fill separate named parameters but struggle with mixed-format strings where they'd need to prefix items correctly. Two params with clear names (`attachment_ids` for 📎 IDs, `urls` for http links) are unambiguous. + +**Alternative considered:** Single `sources` param with prefix-based parsing (`📎abc,https://...`). Rejected for LLM reliability reasons. + +### 2. Virtual attachments built at tool-library level + +Resolve URLs into ephemeral `ChatMessageAttachment` objects in `llm_tool_library.py` before passing them to `ChatAttachmentProcessor` or `ChatImageEditService`. The processors receive the same type they already expect. + +**Why:** Minimizes changes to downstream processors. They don't need to know whether an attachment came from DB or a URL — they just need an object with `last_url`, `mime_type`, and `extension`. + +**Alternative considered:** Teaching each processor to accept raw URLs directly. Rejected because it duplicates resolution logic and touches more code. + +### 3. MIME type detection via HTTP HEAD + URL extension fallback + +For external URLs: +1. Parse extension from URL path (strip query params) +2. Send HTTP HEAD request to get `Content-Type` header +3. Use HEAD response if available, fall back to extension-based lookup from `supported_files.py` +4. Reject if MIME type can't be determined or isn't in `KNOWN_FILE_FORMATS` + +**Why:** HEAD is cheap and gives the authoritative MIME type. Extension fallback handles servers that don't return Content-Type. Rejecting unknown types prevents the pipeline from silently failing on unsupported media. + +### 4. Virtual attachment identity + +Virtual attachments use a deterministic ID derived from a URL hash (e.g., `url-`). This gives: +- Stable cache keys for repeat analysis of the same URL +- Clear distinction from DB-backed attachment IDs (which are UUIDs) +- No collision with real attachment IDs + +The `chat_id` field on virtual attachments will use the current invoker's chat ID. + +### 5. Processor changes — accept pre-resolved attachments + +Both `ChatAttachmentProcessor` and `ChatImageEditService` currently resolve attachments from IDs internally. They need a second entry path that accepts already-resolved `ChatMessageAttachment` objects (the virtual ones). + +Approach: Add an optional `pre_resolved_attachments` parameter to both. When provided, these skip DB lookup and platform refresh. The existing `attachment_ids` path remains unchanged. + +### 6. Rename `process_attachments` → `process_media` + +The tool name and `ALL_LLM_TOOLS` key change. The docstring is updated to mention URLs. The `attachment_ids` parameter keeps its name and description (📎 IDs), and `urls` is added alongside it. + +## Risks / Trade-offs + +**Large file downloads** → No mitigation beyond HTTP timeouts. Users could paste URLs to very large files. The existing `requests.get()` in processors already loads content into memory. This is a pre-existing concern, not introduced by this change. + +**URL liveness** → External URLs may go stale between processing calls. Unlike platform attachments (which get refreshed), there's no refresh mechanism. Acceptable for ephemeral, non-persisted media. + +**HEAD request may be blocked** → Some servers block HEAD or return different Content-Type than the actual content. The extension fallback mitigates this. If both fail, the tool returns an error to the LLM. + +**Cache key stability** → Same URL may serve different content over time. The cache TTL (13 weeks, matching existing attachment cache) is long. Acceptable trade-off — users can re-send the URL if content changed. diff --git a/openspec/changes/archive/2026-05-16-add-external-media-urls/proposal.md b/openspec/changes/archive/2026-05-16-add-external-media-urls/proposal.md new file mode 100644 index 00000000..e277799c --- /dev/null +++ b/openspec/changes/archive/2026-05-16-add-external-media-urls/proposal.md @@ -0,0 +1,32 @@ +## Why + +The `process_attachments` LLM tool only accepts chat message attachment IDs (files sent via Telegram/WhatsApp). When a user pastes a URL to an image, audio file, or document in their message, the agent has no way to process that media — it can only fetch web page text via `fetch_web_content`. This means users can't say "analyze this image: https://example.com/photo.jpg" or "edit this photo: https://imgur.com/abc.png" and get the same media processing they'd get from sending the file directly. + +## What Changes + +- Rename `process_attachments` to `process_media` in the LLM tool library +- Add a new `urls` parameter (comma-separated list of external URLs) alongside the existing `attachment_ids` parameter +- Build URL-to-virtual-attachment resolution: HTTP HEAD for MIME type detection, extension parsing from URL path, construction of ephemeral `ChatMessageAttachment` objects +- Modify `ChatAttachmentProcessor` to accept pre-resolved attachments (skip DB lookup and platform refresh for virtual attachments) +- Modify `ChatImageEditService` to accept pre-resolved attachments (same skip logic) +- Use URL hash as cache key for URL-based inputs (same caching infra, no DB persistence) +- Both `analyze` and `image-edit` operations work with external URLs + +## Capabilities + +### New Capabilities + +- `external-url-media-resolution`: Resolving external URLs into virtual `ChatMessageAttachment` objects with MIME type detection and extension parsing, usable by existing media processing pipelines + +### Modified Capabilities + +None. + +## Impact + +- `src/features/chat/llm_tools/llm_tool_library.py` — rename function, add `urls` param, URL resolution logic +- `src/features/chat/chat_attachment_processor.py` — accept pre-resolved attachments, skip DB/refresh for them +- `src/features/chat/chat_image_edit_service.py` — accept pre-resolved attachments, skip DB/refresh for them +- `src/di/di.py` — update factory signatures if needed +- Tests for all modified files +- No API changes, no DB migrations, no new dependencies diff --git a/openspec/changes/archive/2026-05-16-add-external-media-urls/specs/external-url-media-resolution/spec.md b/openspec/changes/archive/2026-05-16-add-external-media-urls/specs/external-url-media-resolution/spec.md new file mode 100644 index 00000000..353a704a --- /dev/null +++ b/openspec/changes/archive/2026-05-16-add-external-media-urls/specs/external-url-media-resolution/spec.md @@ -0,0 +1,61 @@ +## ADDED Requirements + +### Requirement: LLM tool accepts external URLs for media processing + +The `process_media` tool SHALL accept an optional `urls` parameter containing a comma-separated list of external URLs. At least one of `attachment_ids` or `urls` MUST be provided. Both MAY be provided simultaneously, in which case results from both sources are merged. + +#### Scenario: URL-only media analysis +- **WHEN** the LLM calls `process_media` with `urls: "https://example.com/photo.jpg"` and `operation: "analyze"` +- **THEN** the system fetches the URL, detects its MIME type, routes it through the image analysis pipeline, and returns a text description + +#### Scenario: URL-only image editing +- **WHEN** the LLM calls `process_media` with `urls: "https://example.com/photo.jpg"` and `operation: "image-edit"` +- **THEN** the system fetches the URL, treats it as a reference image, and generates an edited image delivered to the user + +#### Scenario: Mixed attachments and URLs +- **WHEN** the LLM calls `process_media` with both `attachment_ids: "📎abc-123"` and `urls: "https://example.com/photo.jpg"` and `operation: "analyze"` +- **THEN** the system processes both sources and returns combined results for all inputs + +#### Scenario: Neither parameter provided +- **WHEN** the LLM calls `process_media` with both `attachment_ids` and `urls` empty or missing +- **THEN** the system returns a validation error indicating at least one source is required + +### Requirement: External URLs are resolved into virtual attachments + +The system SHALL resolve each external URL into an ephemeral `ChatMessageAttachment` object with a deterministic ID (`url-`), the URL as `last_url`, and detected MIME type and extension. These virtual attachments SHALL NOT be persisted to the database. + +#### Scenario: MIME type detected from HTTP HEAD +- **WHEN** an external URL is resolved and the server responds to HTTP HEAD with a valid `Content-Type` header matching a known format +- **THEN** the virtual attachment uses that MIME type and the corresponding extension + +#### Scenario: MIME type detected from URL extension +- **WHEN** an external URL is resolved and the HTTP HEAD either fails or returns no usable `Content-Type`, but the URL path contains a recognized file extension +- **THEN** the virtual attachment uses the MIME type mapped from that extension via `supported_files.py` + +#### Scenario: MIME type cannot be determined +- **WHEN** an external URL is resolved and neither HTTP HEAD nor URL extension yields a known MIME type +- **THEN** the system returns an error for that URL indicating the media type is unsupported + +### Requirement: Virtual attachments skip DB lookup and platform refresh + +When `ChatAttachmentProcessor` or `ChatImageEditService` receive pre-resolved virtual attachments, they SHALL skip the database lookup and platform SDK refresh steps. The processing pipeline (image analysis, audio transcription, document search, image editing) SHALL work identically regardless of attachment source. + +#### Scenario: Analyze path with virtual attachment +- **WHEN** `ChatAttachmentProcessor` receives a virtual attachment with a valid image URL and MIME type +- **THEN** it processes the image through computer vision analysis without any DB or platform SDK calls + +#### Scenario: Image-edit path with virtual attachment +- **WHEN** `ChatImageEditService` receives a virtual attachment with a valid image URL +- **THEN** it passes the URL to `ImageEditor` without calling `refresh_attachments_by_ids` + +### Requirement: URL-based results are cached using URL hash + +The system SHALL cache analysis results for URL-based inputs using the URL's MD5 hash as the cache key (same prefix and TTL as attachment-based caching). Repeat analysis of the same URL with the same context within the cache TTL SHALL return cached results. + +#### Scenario: Cache hit on repeated URL analysis +- **WHEN** the same URL is analyzed twice with the same context within the cache TTL +- **THEN** the second call returns cached results without re-fetching or re-analyzing + +#### Scenario: Cache miss on different context +- **WHEN** the same URL is analyzed with a different context string +- **THEN** the system re-fetches and re-analyzes, producing a new cache entry diff --git a/openspec/changes/archive/2026-05-16-add-external-media-urls/tasks.md b/openspec/changes/archive/2026-05-16-add-external-media-urls/tasks.md new file mode 100644 index 00000000..603f3ad1 --- /dev/null +++ b/openspec/changes/archive/2026-05-16-add-external-media-urls/tasks.md @@ -0,0 +1,22 @@ +## 1. URL Resolution + +- [x] 1.1 Add URL-to-virtual-attachment resolution function in `llm_tool_library.py`: HTTP HEAD for MIME type, extension parsing from URL path, fallback to `supported_files.py` lookup, build ephemeral `ChatMessageAttachment` with `url-` ID +- [x] 1.2 Add validation: reject URLs whose MIME type can't be determined or isn't in `KNOWN_FILE_FORMATS` + +## 2. LLM Tool Changes + +- [x] 2.1 Rename `process_attachments` → `process_media` in function name, `ALL_LLM_TOOLS` dict key, and docstring; add `urls: str | None` parameter; update docstring to describe both input sources +- [x] 2.2 Wire up `process_media` to resolve URLs into virtual attachments and merge with DB-resolved attachments before passing to processors; validate that at least one of `attachment_ids` or `urls` is provided + +## 3. Processor Changes + +- [x] 3.1 Modify `ChatAttachmentProcessor.__init__` to accept optional `pre_resolved_attachments` list; when provided, skip DB lookup and platform refresh for those; merge with any DB-resolved attachments +- [x] 3.2 Modify `ChatImageEditService.__init__` to accept optional `pre_resolved_attachments` list; when provided, skip `refresh_attachments_by_ids` for those; merge with any DB-resolved attachments +- [x] 3.3 Update DI factory methods (`chat_attachment_processor`, `chat_image_edit_service`) to pass through the new parameter + +## 4. Tests + +- [x] 4.1 Add tests for URL resolution: MIME from HEAD, MIME from extension fallback, unsupported type rejection, malformed URL handling +- [x] 4.2 Add tests for `process_media`: URL-only, attachment-only, mixed, neither (validation error) +- [x] 4.3 Add tests for `ChatAttachmentProcessor` with pre-resolved virtual attachments (verify no DB/SDK calls) +- [x] 4.4 Add tests for `ChatImageEditService` with pre-resolved virtual attachments (verify no DB/SDK calls) From 726099c1f48e680ea3528b87abc8158fcd312d54 Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:46:47 +0200 Subject: [PATCH 05/10] RELEASE 5.13.0 - The Agent can now fetch pages from the internet including remote photos, and also use those remote photos for editing and analysis, all working together with already supported chat attachments! --- docs/open-api-docs.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml index 351f0ac6..8cfc1a1c 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.12.3 + version: 5.13.0 license: name: MIT url: https://opensource.org/licenses/MIT diff --git a/pyproject.toml b/pyproject.toml index 2c3d55ca..23392e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "the-agent" -version = "5.12.3" +version = "5.13.0" [tool.setuptools] package-dir = {"" = "src"} From 39c2613ebfe69a5fee85aa809452f809ecf74bd1 Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:47:24 +0200 Subject: [PATCH 06/10] Bump version --- docs/open-api-docs.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml index 8cfc1a1c..fb92985f 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.13.0 + version: 5.13.1 license: name: MIT url: https://opensource.org/licenses/MIT diff --git a/pyproject.toml b/pyproject.toml index 23392e66..fbe68655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "the-agent" -version = "5.13.0" +version = "5.13.1" [tool.setuptools] package-dir = {"" = "src"} From a65da5a0a14e3bb6e5a42587436b53f9794bcd9a Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:58:32 +0200 Subject: [PATCH 07/10] Fix phone number stripping in WhatsApp to remove leading '00' Co-Authored-By: Claude Opus 4.6 --- src/util/functions.py | 5 ++++- test/util/test_functions.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/util/functions.py b/src/util/functions.py index 5b0942cf..a7473593 100644 --- a/src/util/functions.py +++ b/src/util/functions.py @@ -66,7 +66,10 @@ def digest_md5(content: str) -> str: def normalize_phone_number(phone: str | None) -> str | None: if phone is None: return None - return "".join(c for c in phone if c.isdigit()) + digits = "".join(c for c in phone if c.isdigit()) + if digits.startswith("00"): + digits = digits[2:] + return digits def normalize_username(username: str | None) -> str | None: diff --git a/test/util/test_functions.py b/test/util/test_functions.py index 2cd5bfed..be00ed13 100644 --- a/test/util/test_functions.py +++ b/test/util/test_functions.py @@ -265,6 +265,10 @@ def test_normalize_phone_number_mixed_chars(self): from util.functions import normalize_phone_number self.assertEqual(normalize_phone_number("wa:+38 044-123-45-67 ext.89"), "38044123456789") + def test_normalize_phone_number_strips_leading_double_zero(self): + from util.functions import normalize_phone_number + self.assertEqual(normalize_phone_number("0049123456789"), "49123456789") + def test_normalize_username_none(self): from util.functions import normalize_username self.assertIsNone(normalize_username(None)) From a81fbf4b5aaaa099d35aa4b5befce9a1fa16184b Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 20:59:07 +0200 Subject: [PATCH 08/10] Bump version Co-Authored-By: Claude Opus 4.6 --- docs/open-api-docs.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml index fb92985f..30ad496d 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.13.1 + version: 5.13.2 license: name: MIT url: https://opensource.org/licenses/MIT diff --git a/pyproject.toml b/pyproject.toml index fbe68655..27a25e72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "the-agent" -version = "5.13.1" +version = "5.13.2" [tool.setuptools] package-dir = {"" = "src"} From f2bb0304b4d3ae7f32b255bb1015c153541d6718 Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 21:18:06 +0200 Subject: [PATCH 09/10] Add User-Agent headers to external media download requests Co-Authored-By: Claude Opus 4.6 --- src/features/audio/audio_transcriber.py | 3 ++- src/features/chat/chat_attachment_processor.py | 5 +++-- src/features/chat/url_attachment_resolver.py | 8 +++++++- src/features/images/image_editor.py | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/features/audio/audio_transcriber.py b/src/features/audio/audio_transcriber.py index 9e703669..aacafc49 100644 --- a/src/features/audio/audio_transcriber.py +++ b/src/features/audio/audio_transcriber.py @@ -17,6 +17,7 @@ from features.external_tools.configured_tool import ConfiguredTool from features.external_tools.external_tool import ToolType from features.integrations import prompt_resolvers +from features.web_browsing.web_fetcher import DEFAULT_HEADERS from util import log from util.error_codes import LLM_UNEXPECTED_RESPONSE from util.errors import ExternalServiceError @@ -57,7 +58,7 @@ def __init__( def __validate_content(self, audio_url: str, audio_content: bytes | None): log.t(f"Fetching and validating audio from URL '{audio_url}'") - self.__audio_content = audio_content or requests.get(audio_url).content + self.__audio_content = audio_content or requests.get(audio_url, headers = DEFAULT_HEADERS).content if self.__extension not in SUPPORTED_AUDIO_FORMATS.keys(): log.t(f" Unsupported audio format: '.{self.__extension}'") diff --git a/src/features/chat/chat_attachment_processor.py b/src/features/chat/chat_attachment_processor.py index 9b71451a..fd88c104 100644 --- a/src/features/chat/chat_attachment_processor.py +++ b/src/features/chat/chat_attachment_processor.py @@ -13,6 +13,7 @@ from features.documents.document_search import DocumentSearch from features.external_tools.intelligence_presets import default_tool_for from features.images.computer_vision_analyzer import ComputerVisionAnalyzer +from features.web_browsing.web_fetcher import DEFAULT_HEADERS from util import log from util.functions import digest_md5 @@ -125,7 +126,7 @@ def __process_images(self, image_attachments: list[ChatMessageAttachment], indic image_b64s: list[str] = [] image_mime_types: list[str] = [] for attachment in image_attachments: - contents = requests.get(str(attachment.last_url)).content + contents = requests.get(str(attachment.last_url), headers = DEFAULT_HEADERS).content image_b64s.append(base64.b64encode(contents).decode("utf-8")) image_mime_types.append(str(attachment.mime_type)) configured_tool = self.__di.tool_choice_resolver.require_tool( @@ -187,7 +188,7 @@ def fetch_text_content(self, attachment: ChatMessageAttachment) -> str | None: log.t(f"Resolving text content for attachment '{attachment.id}'") # fetching binary contents will also validate the URL - contents = requests.get(str(attachment.last_url)).content + contents = requests.get(str(attachment.last_url), headers = DEFAULT_HEADERS).content # handle audio if attachment.mime_type in KNOWN_AUDIO_FORMATS.values() or attachment.extension in KNOWN_AUDIO_FORMATS.keys(): diff --git a/src/features/chat/url_attachment_resolver.py b/src/features/chat/url_attachment_resolver.py index dbd34c77..99c2eb0c 100644 --- a/src/features/chat/url_attachment_resolver.py +++ b/src/features/chat/url_attachment_resolver.py @@ -6,6 +6,7 @@ from db.schema.chat_message_attachment import ChatMessageAttachment from di.di import DI from features.chat.supported_files import KNOWN_FILE_FORMATS +from features.web_browsing.web_fetcher import DEFAULT_HEADERS from util.config import config from util.error_codes import UNSUPPORTED_MEDIA_TYPE from util.errors import ValidationError @@ -41,7 +42,12 @@ def execute(self) -> ChatMessageAttachment: def __mime_from_head(self) -> str | None: try: - response = requests.head(self.__url, timeout = config.web_timeout_s, allow_redirects = True) + response = requests.head( + self.__url, + headers = DEFAULT_HEADERS, + timeout = config.web_timeout_s, + allow_redirects = True, + ) content_type = response.headers.get("Content-Type", "") if content_type: candidate = content_type.split(";")[0].strip() diff --git a/src/features/images/image_editor.py b/src/features/images/image_editor.py index ea16cc98..4b058c51 100644 --- a/src/features/images/image_editor.py +++ b/src/features/images/image_editor.py @@ -15,6 +15,7 @@ from features.external_tools.external_tool_provider_library import GOOGLE_AI, REPLICATE, XAI from features.images.image_api_utils import filter_replicate_params, map_to_model_parameters from features.images.image_size_utils import calculate_image_size_category +from features.web_browsing.web_fetcher import DEFAULT_HEADERS from util import log from util.config import config from util.error_codes import EXTERNAL_EMPTY_RESPONSE, TOO_MANY_INPUT_IMAGES, UNEXPECTED_ERROR, UNSUPPORTED_PROVIDER @@ -79,7 +80,7 @@ def execute(self) -> str | None: for url, mime_type in zip(self.__image_urls, self.__input_mime_types): suffix = self.__get_suffix(url, mime_type) temp_file = tempfile.NamedTemporaryFile(delete = False, suffix = suffix) - response = requests.get(url) + response = requests.get(url, headers = DEFAULT_HEADERS) temp_file.write(response.content) temp_file.flush() temp_file.close() From 7a3c6251a4bdabebb6b485a0acac5f77b5822e18 Mon Sep 17 00:00:00 2001 From: Milos Marinkovic Date: Sat, 16 May 2026 21:18:26 +0200 Subject: [PATCH 10/10] Bump version Co-Authored-By: Claude Opus 4.6 --- docs/open-api-docs.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/open-api-docs.yaml b/docs/open-api-docs.yaml index 30ad496d..ba7ae094 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.13.2 + version: 5.13.3 license: name: MIT url: https://opensource.org/licenses/MIT diff --git a/pyproject.toml b/pyproject.toml index 27a25e72..2a392b6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "the-agent" -version = "5.13.2" +version = "5.13.3" [tool.setuptools] package-dir = {"" = "src"}