diff options
Diffstat (limited to 'continuedev')
| -rw-r--r-- | continuedev/poetry.lock | 340 | ||||
| -rw-r--r-- | continuedev/pyproject.toml | 2 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/autopilot.py | 138 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/config.py | 3 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/context.py | 205 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/context_manager.py | 119 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/main.py | 59 | ||||
| -rw-r--r-- | continuedev/src/continuedev/core/sdk.py | 31 | ||||
| -rw-r--r-- | continuedev/src/continuedev/libs/context_providers/highlighted_code_context_provider.py | 191 | ||||
| -rw-r--r-- | continuedev/src/continuedev/libs/util/paths.py | 20 | ||||
| -rw-r--r-- | continuedev/src/continuedev/models/generate_json_schema.py | 3 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/gui.py | 37 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/gui_protocol.py | 10 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/main.py | 28 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/meilisearch_server.py | 56 | ||||
| -rw-r--r-- | continuedev/src/continuedev/server/session_manager.py | 6 | 
16 files changed, 949 insertions, 299 deletions
diff --git a/continuedev/poetry.lock b/continuedev/poetry.lock index 1cd4a591..b67128fd 100644 --- a/continuedev/poetry.lock +++ b/continuedev/poetry.lock @@ -221,6 +221,24 @@ files = [  ]  [[package]] +name = "camel-converter" +version = "3.0.2" +description = "Converts a string from snake case to camel case or camel case to snake case" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ +    {file = "camel_converter-3.0.2-py3-none-any.whl", hash = "sha256:88e5d91be5b2dff9c0748ba515774c3421088922d9e77c39f8742eb41cb7db88"}, +    {file = "camel_converter-3.0.2.tar.gz", hash = "sha256:3b3d076e824ae979b271b4d497c90514c2b218811f76b0c368fb69da2556fe07"}, +] + +[package.dependencies] +pydantic = {version = ">=1.8.2", optional = true, markers = "extra == \"pydantic\""} + +[package.extras] +pydantic = ["pydantic (>=1.8.2)"] + +[[package]]  name = "certifi"  version = "2022.12.7"  description = "Python package for providing Mozilla's CA Bundle." @@ -233,6 +251,83 @@ files = [  ]  [[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" +files = [ +    {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, +    {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, +    {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, +    {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, +    {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, +    {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, +    {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, +    {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, +    {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, +    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, +    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, +    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, +    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, +    {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, +    {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, +    {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, +    {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, +    {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, +    {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, +    {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, +    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, +    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, +    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, +    {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, +    {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, +    {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, +    {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, +    {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, +    {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, +    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, +    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, +    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, +    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, +    {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, +    {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, +    {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, +    {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, +    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, +    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, +    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, +    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, +    {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, +    {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, +    {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, +    {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, +    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, +    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, +    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, +    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, +    {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, +    {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, +    {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, +    {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, +    {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, +    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, +    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, +    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, +    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, +    {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, +    {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, +    {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, +    {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, +    {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, +    {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]]  name = "charset-normalizer"  version = "3.1.0"  description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." @@ -357,6 +452,52 @@ files = [  ]  [[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ +    {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, +    {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, +    {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, +    {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, +    {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, +    {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, +    {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, +    {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, +    {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, +    {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, +    {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, +    {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, +    {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, +    {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, +    {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, +    {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, +    {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, +    {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, +    {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, +    {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, +    {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, +    {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, +    {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]]  name = "dataclasses-json"  version = "0.5.7"  description = "Easily serialize dataclasses to and from JSON" @@ -377,6 +518,24 @@ typing-inspect = ">=0.4.0"  dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=6.2.3)", "simplejson", "types-dataclasses"]  [[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ +    {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, +    {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + +[[package]]  name = "diff-match-patch"  version = "20230430"  description = "Diff Match and Patch" @@ -817,6 +976,22 @@ files = [  marshmallow = ">=2.0.0"  [[package]] +name = "meilisearch" +version = "0.28.1" +description = "The python client for Meilisearch API." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ +    {file = "meilisearch-0.28.1-py3-none-any.whl", hash = "sha256:b4cfaf140fc323c429a4e174b801bcc6dddef88bf901560ebf2fc5c0014099b5"}, +    {file = "meilisearch-0.28.1.tar.gz", hash = "sha256:4e8627abe1394f04125c99e60f9f4356ba200effe5735ce9b8a44b09e3c6403c"}, +] + +[package.dependencies] +camel-converter = {version = "*", extras = ["pydantic"]} +requests = "*" + +[[package]]  name = "monotonic"  version = "1.6"  description = "An implementation of time.monotonic() for Python 2 & < 3.3" @@ -1199,6 +1374,18 @@ files = [  test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]  [[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ +    {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, +    {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]]  name = "pydantic"  version = "1.10.7"  description = "Data validation and settings management using python type hints" @@ -1252,6 +1439,72 @@ dotenv = ["python-dotenv (>=0.10.4)"]  email = ["email-validator (>=1.0.3)"]  [[package]] +name = "pygithub" +version = "1.59.0" +description = "Use the full Github API v3" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ +    {file = "PyGithub-1.59.0-py3-none-any.whl", hash = "sha256:126bdbae72087d8d038b113aab6b059b4553cb59348e3024bb1a1cae406ace9e"}, +    {file = "PyGithub-1.59.0.tar.gz", hash = "sha256:6e05ff49bac3caa7d1d6177a10c6e55a3e20c85b92424cc198571fd0cf786690"}, +] + +[package.dependencies] +deprecated = "*" +pyjwt = {version = ">=2.4.0", extras = ["crypto"]} +pynacl = ">=1.4.0" +requests = ">=2.14.0" + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ +    {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, +    {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ +    {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, +    {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, +    {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]]  name = "pyrsistent"  version = "0.19.3"  description = "Persistent/Functional/Immutable data structures" @@ -1936,6 +2189,91 @@ files = [  ]  [[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ +    {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, +    {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, +    {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, +    {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, +    {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, +    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, +    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, +    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, +    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, +    {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, +    {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, +    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, +    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, +    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, +    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, +    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, +    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, +    {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, +    {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, +    {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, +    {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, +    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, +    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, +    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, +    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, +    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, +    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, +    {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, +    {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, +    {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, +    {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, +    {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, +    {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, +    {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, +    {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, +    {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, +    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, +    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, +    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, +    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, +    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, +    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, +    {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, +    {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, +    {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, +    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, +    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, +    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, +    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, +    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, +    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, +    {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, +    {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, +    {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, +    {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, +    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, +    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, +    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, +    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, +    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, +    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, +    {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, +    {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, +    {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, +    {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, +    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, +    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, +    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, +    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, +    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, +    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, +    {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, +    {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, +    {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, +    {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]]  name = "yarl"  version = "1.9.2"  description = "Yet another URL library" @@ -2042,4 +2380,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p  [metadata]  lock-version = "2.0"  python-versions = "^3.8.1" -content-hash = "3fcd19c11b9c338a181e591b56e21d59c7834abff431fb9f40cc1ea874b64557" +content-hash = "83880c8de27a4094fa8e695bd8db5d4053fc2d61ffc028f09cfa097f315c35f2" diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 0abc9504..64d5e90c 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -27,6 +27,8 @@ directory-tree = "^0.0.3.1"  anthropic = "^0.3.4"  chevron = "^0.14.0"  psutil = "^5.9.5" +meilisearch = "^0.28.1" +pygithub = "^1.59.0"  [tool.poetry.scripts]  typegen = "src.continuedev.models.generate_json_schema:main"  diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index abda50b0..c0f95414 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -9,10 +9,12 @@ from pydantic import root_validator  from ..models.filesystem import RangeInFileWithContents  from ..models.filesystem_edit import FileEditWithFullContents  from .observation import Observation, InternalErrorObservation +from .context import ContextItem, ContextItemDescription, ContextItemId, ContextManager +from ..libs.context_providers.highlighted_code_context_provider import HighlightedCodeContextProvider  from ..server.ide_protocol import AbstractIdeProtocolServer  from ..libs.util.queue import AsyncSubscriptionQueue  from ..models.main import ContinueBaseModel -from .main import Context, ContinueCustomException, HighlightedRangeContext, Policy, History, FullState, Step, HistoryNode +from .main import Context, ContinueCustomException, Policy, History, FullState, Step, HistoryNode  from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep  from ..libs.util.telemetry import capture_event  from .sdk import ContinueSDK @@ -47,10 +49,11 @@ class Autopilot(ContinueBaseModel):      history: History = History.from_empty()      context: Context = Context()      full_state: Union[FullState, None] = None -    _on_update_callbacks: List[Callable[[FullState], None]] = [] - +    context_manager: Union[ContextManager, None] = None      continue_sdk: ContinueSDK = None +    _on_update_callbacks: List[Callable[[FullState], None]] = [] +      _active: bool = False      _should_halt: bool = False      _main_user_input_queue: List[str] = [] @@ -62,6 +65,14 @@ class Autopilot(ContinueBaseModel):      async def create(cls, policy: Policy, ide: AbstractIdeProtocolServer, full_state: FullState) -> "Autopilot":          autopilot = cls(ide=ide, policy=policy)          autopilot.continue_sdk = await ContinueSDK.create(autopilot) + +        # Load documents into the search index +        autopilot.context_manager = ContextManager( +            autopilot.continue_sdk.config.context_providers + [ +                HighlightedCodeContextProvider(ide=ide) +            ]) +        await autopilot.context_manager.load_index() +          return autopilot      class Config: @@ -75,15 +86,16 @@ class Autopilot(ContinueBaseModel):              values['history'] = full_state.history          return values -    def get_full_state(self) -> FullState: +    async def get_full_state(self) -> FullState:          full_state = FullState(              history=self.history,              active=self._active,              user_input_queue=self._main_user_input_queue,              default_model=self.continue_sdk.config.default_model, -            highlighted_ranges=self._highlighted_ranges,              slash_commands=self.get_available_slash_commands(), -            adding_highlighted_code=self._adding_highlighted_code, +            adding_highlighted_code=self.context_manager.context_providers[ +                "code"].adding_highlighted_code, +            selected_context_items=await self.context_manager.get_selected_items()          )          self.full_state = full_state          return full_state @@ -104,8 +116,8 @@ class Autopilot(ContinueBaseModel):          self._main_user_input_queue = []          self._active = False -        # Also remove all context -        self._highlighted_ranges = [] +        # Clear context +        await self.context_manager.clear_context()          await self.update_subscribers() @@ -114,7 +126,7 @@ class Autopilot(ContinueBaseModel):          self._on_update_callbacks.append(callback)      async def update_subscribers(self): -        full_state = self.get_full_state() +        full_state = await self.get_full_state()          for callback in self._on_update_callbacks:              await callback(full_state) @@ -159,81 +171,10 @@ class Autopilot(ContinueBaseModel):                      step = tb_step.step({"output": output, **tb_step.params})                      await self._run_singular_step(step) -    _highlighted_ranges: List[HighlightedRangeContext] = [] -    _adding_highlighted_code: bool = False - -    def _make_sure_is_editing_range(self): -        """If none of the highlighted ranges are currently being edited, the first should be selected""" -        if len(self._highlighted_ranges) == 0: -            return -        if not any(map(lambda x: x.editing, self._highlighted_ranges)): -            self._highlighted_ranges[0].editing = True - -    def _disambiguate_highlighted_ranges(self): -        """If any files have the same name, also display their folder name""" -        name_status: Dict[str, set] = { -        }  # basename -> set of full paths with that basename -        for rif in self._highlighted_ranges: -            basename = os.path.basename(rif.range.filepath) -            if basename in name_status: -                name_status[basename].add(rif.range.filepath) -            else: -                name_status[basename] = {rif.range.filepath} - -        for rif in self._highlighted_ranges: -            basename = os.path.basename(rif.range.filepath) -            if len(name_status[basename]) > 1: -                rif.display_name = os.path.join( -                    os.path.basename(os.path.dirname(rif.range.filepath)), basename) -            else: -                rif.display_name = basename -      async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]): -        # Filter out rifs from ~/.continue/diffs folder -        range_in_files = [ -            rif for rif in range_in_files if not os.path.dirname(rif.filepath) == os.path.expanduser("~/.continue/diffs")] - -        # Make sure all filepaths are relative to workspace -        workspace_path = self.continue_sdk.ide.workspace_directory - -        # If not adding highlighted code -        if not self._adding_highlighted_code: -            if len(self._highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end): -                # If un-highlighting the range to edit, then remove the range -                self._highlighted_ranges = [] -                await self.update_subscribers() -            elif len(range_in_files) > 0: -                # Otherwise, replace the current range with the new one -                # This is the first range to be highlighted -                self._highlighted_ranges = [HighlightedRangeContext( -                    range=range_in_files[0], editing=True, pinned=False, display_name=os.path.basename(range_in_files[0].filepath))] -                await self.update_subscribers() -            return - -        # If current range overlaps with any others, delete them and only keep the new range -        new_ranges = [] -        for i, rif in enumerate(self._highlighted_ranges): -            found_overlap = False -            for new_rif in range_in_files: -                if rif.range.filepath == new_rif.filepath and rif.range.range.overlaps_with(new_rif.range): -                    found_overlap = True -                    break - -                # Also don't allow multiple ranges in same file with same content. This is useless to the model, and avoids -                # the bug where cmd+f causes repeated highlights -                if rif.range.filepath == new_rif.filepath and rif.range.contents == new_rif.contents: -                    found_overlap = True -                    break - -            if not found_overlap: -                new_ranges.append(rif) - -        self._highlighted_ranges = new_ranges + [HighlightedRangeContext( -            range=rif, editing=False, pinned=False, display_name=os.path.basename(rif.filepath) -        ) for rif in range_in_files] - -        self._make_sure_is_editing_range() -        self._disambiguate_highlighted_ranges() +        # Add to context manager +        await self.context_manager.context_providers["code"].handle_highlighted_code( +            range_in_files)          await self.update_subscribers() @@ -250,29 +191,16 @@ class Autopilot(ContinueBaseModel):          await self.update_subscribers() -    async def delete_context_at_indices(self, indices: List[int]): -        kept_ranges = [] -        for i, rif in enumerate(self._highlighted_ranges): -            if i not in indices: -                kept_ranges.append(rif) -        self._highlighted_ranges = kept_ranges - -        self._make_sure_is_editing_range() - +    async def delete_context_with_ids(self, ids: List[str]): +        await self.context_manager.delete_context_with_ids(ids)          await self.update_subscribers()      async def toggle_adding_highlighted_code(self): -        self._adding_highlighted_code = not self._adding_highlighted_code -        await self.update_subscribers() - -    async def set_editing_at_indices(self, indices: List[int]): -        for i in range(len(self._highlighted_ranges)): -            self._highlighted_ranges[i].editing = i in indices +        self.context_manager.context_providers["code"].adding_highlighted_code = not self.context_manager.context_providers["code"].adding_highlighted_code          await self.update_subscribers() -    async def set_pinned_at_indices(self, indices: List[int]): -        for i in range(len(self._highlighted_ranges)): -            self._highlighted_ranges[i].pinned = i in indices +    async def set_editing_at_ids(self, ids: List[str]): +        self.context_manager.context_providers["code"].set_editing_at_ids(ids)          await self.update_subscribers()      async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: @@ -437,10 +365,6 @@ class Autopilot(ContinueBaseModel):          if len(self._main_user_input_queue) > 1:              return -        # Remove context unless pinned -        # self._highlighted_ranges = [ -        #     hr for hr in self._highlighted_ranges if hr.pinned] -          # await self._request_halt()          # Just run the step that takes user input, and          # then up to the policy to decide how to deal with it. @@ -456,3 +380,7 @@ class Autopilot(ContinueBaseModel):          await self._request_halt()          await self.reverse_to_index(index)          await self.run_from_step(UserInputStep(user_input=user_input)) + +    async def select_context_item(self, id: str, query: str): +        await self.context_manager.select_context_item(id, query) +        await self.update_subscribers() diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 54f15143..bb9ca323 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -1,6 +1,7 @@  import json  import os  from .main import Step +from .context import ContextProvider  from pydantic import BaseModel, validator  from typing import List, Literal, Optional, Dict, Type, Union  import yaml @@ -50,6 +51,8 @@ class ContinueConfig(BaseModel):      system_message: Optional[str] = None      azure_openai_info: Optional[AzureInfo] = None +    context_providers: List[ContextProvider] = [] +      # Want to force these to be the slash commands for now      @validator('slash_commands', pre=True)      def default_slash_commands_validator(cls, v): diff --git a/continuedev/src/continuedev/core/context.py b/continuedev/src/continuedev/core/context.py new file mode 100644 index 00000000..67bba651 --- /dev/null +++ b/continuedev/src/continuedev/core/context.py @@ -0,0 +1,205 @@ + +from abc import abstractmethod +from typing import Dict, List +import meilisearch +from pydantic import BaseModel + + +from .main import ChatMessage, ContextItem, ContextItemDescription, ContextItemId +from ..server.meilisearch_server import check_meilisearch_running + + +SEARCH_INDEX_NAME = "continue_context_items" + + +class ContextProvider(BaseModel): +    """ +    The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. +    When you type '@', the context provider will be asked to populate a list of options. +    These options will be updated on each keystroke. +    When you hit enter on an option, the context provider will add that item to the autopilot's list of context (which is all stored in the ContextManager object). +    """ + +    title: str + +    selected_items: List[ContextItem] = [] + +    async def get_selected_items(self) -> List[ContextItem]: +        """ +        Returns all of the selected ContextItems. + +        Default implementation simply returns self.selected_items. + +        Other implementations may add an async processing step. +        """ +        return self.selected_items + +    @abstractmethod +    async def provide_context_items(self) -> List[ContextItem]: +        """ +        Provide documents for search index. This is run on startup. + +        This is the only method that must be implemented. +        """ + +    async def get_chat_messages(self) -> List[ChatMessage]: +        """ +        Returns all of the chat messages for the context provider. + +        Default implementation has a string template. +        """ +        return [ChatMessage(role="user", content=f"{item.description.name}: {item.description.description}\n\n{item.content}", summary=item.description.description) for item in await self.get_selected_items()] + +    async def get_item(self, id: ContextItemId, query: str, search_client: meilisearch.Client) -> ContextItem: +        """ +        Returns the ContextItem with the given id. + +        Default implementation uses the search index to get the item. +        """ +        result = search_client.index( +            SEARCH_INDEX_NAME).get_document(id.to_string()) +        return ContextItem( +            description=ContextItemDescription( +                name=result.name, +                description=result.description, +                id=id +            ), +            content=result.content +        ) + +    async def delete_context_with_ids(self, ids: List[ContextItemId]): +        """ +        Deletes the ContextItems with the given IDs, lets ContextProviders recalculate. + +        Default implementation simply deletes those with the given ids. +        """ +        id_strings = {id.to_string() for id in ids} +        self.selected_items = list( +            filter(lambda item: item.description.id.to_string() not in id_strings, self.selected_items)) + +    async def clear_context(self): +        """ +        Clears all context. + +        Default implementation simply clears the selected items. +        """ +        self.selected_items = [] + +    async def add_context_item(self, id: ContextItemId, query: str, search_client: meilisearch.Client): +        """ +        Adds the given ContextItem to the list of ContextItems. + +        Default implementation simply appends the item, not allowing duplicates. + +        This method also allows you not to have to load all of the information until an item is selected. +        """ + +        # Don't add duplicate context +        for item in self.selected_items: +            if item.description.id.item_id == id.item_id: +                return + +        new_item = await self.get_item(id, query, search_client) +        self.selected_items.append(new_item) + + +class ContextManager: +    """ +    The context manager is responsible for storing the context to be passed to the LLM, including +    - ContextItems (highlighted code, GitHub Issues, etc.) +    - ChatMessages in the history +    - System Message +    - Functions + +    It is responsible for compiling all of this information into a single prompt without exceeding the token limit. +    """ + +    async def get_selected_items(self) -> List[ContextItem]: +        """ +        Returns all of the selected ContextItems. +        """ +        return sum([await provider.get_selected_items() for provider in self.context_providers.values()], []) + +    async def get_chat_messages(self) -> List[ChatMessage]: +        """ +        Returns chat messages from each provider. +        """ +        return sum([await provider.get_chat_messages() for provider in self.context_providers.values()], []) + +    search_client: meilisearch.Client + +    def __init__(self, context_providers: List[ContextProvider]): +        self.search_client = meilisearch.Client('http://localhost:7700') + +        # If meilisearch isn't running, don't use any ContextProviders that might depend on it +        if not check_meilisearch_running(): +            context_providers = list( +                filter(lambda cp: cp.title == "code", context_providers)) + +        self.context_providers = { +            prov.title: prov for prov in context_providers} +        self.provider_titles = { +            provider.title for provider in context_providers} + +    async def load_index(self): +        for _, provider in self.context_providers.items(): +            context_items = await provider.provide_context_items() +            documents = [ +                { +                    "id": item.description.id.to_string(), +                    "name": item.description.name, +                    "description": item.description.description, +                    "content": item.content +                } +                for item in context_items +            ] +            if len(documents) > 0: +                self.search_client.index( +                    SEARCH_INDEX_NAME).add_documents(documents) + +    # def compile_chat_messages(self, max_tokens: int) -> List[Dict]: +    #     """ +    #     Compiles the chat prompt into a single string. +    #     """ +    #     return compile_chat_messages(self.model, self.chat_history, max_tokens, self.prompt, self.functions, self.system_message) + +    async def select_context_item(self, id: str, query: str): +        """ +        Selects the ContextItem with the given id. +        """ +        id: ContextItemId = ContextItemId.from_string(id) +        if id.provider_title not in self.provider_titles: +            raise ValueError( +                f"Context provider with title {id.provider_title} not found") + +        await self.context_providers[id.provider_title].add_context_item(id, query, self.search_client) + +    async def delete_context_with_ids(self, ids: List[str]): +        """ +        Deletes the ContextItems with the given IDs, lets ContextProviders recalculate. +        """ + +        # Group by provider title +        provider_title_to_ids: Dict[str, List[ContextItemId]] = {} +        for id in ids: +            id: ContextItemId = ContextItemId.from_string(id) +            if id.provider_title not in provider_title_to_ids: +                provider_title_to_ids[id.provider_title] = [] +            provider_title_to_ids[id.provider_title].append(id) + +        # Recalculate context for each updated provider +        for provider_title, ids in provider_title_to_ids.items(): +            await self.context_providers[provider_title].delete_context_with_ids(ids) + +    async def clear_context(self): +        """ +        Clears all context. +        """ +        for provider in self.context_providers.values(): +            await self.context_providers[provider.title].clear_context() + + +""" +Should define "ArgsTransformer" and "PromptTransformer" classes for the different LLMs. A standard way for them to ingest the +same format of prompts so you don't have to redo all of this logic. +""" diff --git a/continuedev/src/continuedev/core/context_manager.py b/continuedev/src/continuedev/core/context_manager.py deleted file mode 100644 index 37905535..00000000 --- a/continuedev/src/continuedev/core/context_manager.py +++ /dev/null @@ -1,119 +0,0 @@ - -from abc import ABC, abstractmethod, abstractproperty -from ast import List -from pydantic import BaseModel - -from ..libs.util.count_tokens import compile_chat_messages - - -class ContextItemDescription(BaseModel): -    """ -    A ContextItemDescription is a description of a ContextItem that is displayed to the user when they type '@'. - -    The id can be used to retrieve the ContextItem from the ContextManager. -    """ -    name: str -    description: str -    id:  str - - -class ContextItem(BaseModel): -    """ -    A ContextItem is a single item that is stored in the ContextManager. -    """ -    description: ContextItemDescription -    content: str - - -class ContextManager(ABC): -    """ -    The context manager is responsible for storing the context to be passed to the LLM, including -    - ContextItems (highlighted code, GitHub Issues, etc.) -    - ChatMessages in the history -    - System Message -    - Functions - -    It is responsible for compiling all of this information into a single prompt without exceeding the token limit. -    """ - -    def compile_chat_messages(self, max_tokens: int) -> List[Dict]: -        """ -        Compiles the chat prompt into a single string. -        """ -        return compile_chat_messages(self.model, self.chat_history, max_tokens, self.prompt, self.functions, self.system_message) - - -""" -Should define "ArgsTransformer" and "PromptTransformer" classes for the different LLMs. A standard way for them to ingest the -same format of prompts so you don't have to redo all of this logic. -""" - - -class ContextProvider(ABC): -    """ -    The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. -    When you type '@', the context provider will be asked to populate a list of options. -    These options will be updated on each keystroke. -    When you hit enter on an option, the context provider will add that item to the autopilot's list of context (which is all stored in the ContextManager object). -    """ - -    title: str - -    @abstractmethod -    async def load(self): -        """ -        Loads the ContextProvider, possibly reading persisted data from disk. This will be called on startup. -        """ - -    @abstractmethod -    async def save(self): -        """ -        Saves the ContextProvider, possibly writing persisted data to disk. This will be called upon cache refresh. -        """ - -    @abstractmethod -    async def refresh_cache(self): -        """ -        Refreshes the cache of items. This will be called on startup and periodically. -        """ - -    @abstractmethod -    async def get_item_descriptions(self, query: str) -> List[ContextItemDescription]: -        """ -        Returns a list of options that should be displayed to the user. -        """ - -    @abstractmethod -    async def get_item(self, id: str) -> ContextItem: -        """ -        Returns the ContextItem with the given id. This allows you not to have to load all of the information until an item is selected. -        """ - -    @abstractmethod -    async def should_refresh(self) -> bool: -        """ -        Returns whether the ContextProvider should be refreshed. - -        For example, embeddings might need to be recalculated after commits, -        or GitHub issues might need to be refreshed after a new issue is created. - -        This method will be called every startup? Every once in a while? Every hour? -        User defined? Maybe just have a schedule instead of this method. -        """ - - -class GitHubIssuesContextProvider(ContextProvider): -    """ -    The GitHubIssuesContextProvider is a ContextProvider that allows you to search GitHub issues in a repo. -    """ - -    title = "issues" - -    def __init__(self, repo: str): -        self.repo = repo - -    async def get_item_descriptions(self, query: str) -> List[ContextItemDescription]: -        pass - -    async def get_item(self, id: str) -> ContextItem: -        pass diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 50d01f8d..6c6adccc 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -1,12 +1,11 @@  import json -from textwrap import dedent -from typing import Callable, Coroutine, Dict, Generator, List, Literal, Tuple, Union +from typing import Coroutine, Dict, List, Literal, Union +from pydantic.schema import schema + -from ..models.filesystem import RangeInFileWithContents  from ..models.main import ContinueBaseModel -from pydantic import validator +from pydantic import BaseModel, validator  from .observation import Observation -from pydantic.schema import schema  ChatMessageRole = Literal["assistant", "user", "system", "function"] @@ -201,12 +200,48 @@ class SlashCommandDescription(ContinueBaseModel):      description: str -class HighlightedRangeContext(ContinueBaseModel): -    """Context for a highlighted range""" -    range: RangeInFileWithContents -    editing: bool -    pinned: bool -    display_name: str +class ContextItemId(BaseModel): +    """ +    A ContextItemId is a unique identifier for a ContextItem. +    """ +    provider_title: str +    item_id: str + +    def to_string(self) -> str: +        return f"{self.provider_title}-{self.item_id}" + +    @staticmethod +    def from_string(string: str) -> 'ContextItemId': +        provider_title, item_id = string.split('-') +        return ContextItemId(provider_title=provider_title, item_id=item_id) + + +class ContextItemDescription(BaseModel): +    """ +    A ContextItemDescription is a description of a ContextItem that is displayed to the user when they type '@'. + +    The id can be used to retrieve the ContextItem from the ContextManager. +    """ +    name: str +    description: str +    id:  ContextItemId + + +class ContextItem(BaseModel): +    """ +    A ContextItem is a single item that is stored in the ContextManager. +    """ +    description: ContextItemDescription +    content: str + +    @validator('content', pre=True) +    def content_must_be_string(cls, v): +        if v is None: +            return '' +        return v + +    editing: bool = False +    editable: bool = False  class FullState(ContinueBaseModel): @@ -215,9 +250,9 @@ class FullState(ContinueBaseModel):      active: bool      user_input_queue: List[str]      default_model: str -    highlighted_ranges: List[HighlightedRangeContext]      slash_commands: List[SlashCommandDescription]      adding_highlighted_code: bool +    selected_context_items: List[ContextItem]  class ContinueSDK: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 4100efa6..59f33707 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -3,8 +3,10 @@ from functools import cached_property  from typing import Coroutine, Dict, Union  import os +  from ..steps.core.core import DefaultModelEditCodeStep  from ..models.main import Range +from .context import ContextItem  from .abstract_sdk import AbstractContinueSDK  from .config import ContinueConfig, load_config, load_global_config, update_global_config  from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory @@ -289,28 +291,13 @@ class ContinueSDK(AbstractContinueSDK):      async def get_chat_context(self) -> List[ChatMessage]:          history_context = self.history.to_chat_history() -        highlighted_code = [ -            hr.range for hr in self.__autopilot._highlighted_ranges] - -        preface = "The following code is highlighted" - -        # If no higlighted ranges, use first file as context -        if len(highlighted_code) == 0: -            preface = "The following file is open" -            visible_files = await self.ide.getVisibleFiles() -            if len(visible_files) > 0: -                content = await self.ide.readFile(visible_files[0]) -                highlighted_code = [ -                    RangeInFileWithContents.from_entire_file(visible_files[0], content)] - -        for rif in highlighted_code: -            msg = ChatMessage(content=f"{preface} ({rif.filepath}):\n```\n{rif.contents}\n```", -                              role="user", summary=f"{preface}: {rif.filepath}") - -            # Don't insert after latest user message or function call -            i = -1 -            if len(history_context) > 0 and (history_context[i].role == "user" or history_context[i].role == "function"): -                i -= 1 + +        context_messages: List[ChatMessage] = await self.__autopilot.context_manager.get_chat_messages() + +        # Insert at the end, but don't insert after latest user message or function call +        i = -2 if (len(history_context) > 0 and ( +            history_context[-1].role == "user" or history_context[-1].role == "function")) else -1 +        for msg in context_messages:              history_context.insert(i, msg)          return history_context diff --git a/continuedev/src/continuedev/libs/context_providers/highlighted_code_context_provider.py b/continuedev/src/continuedev/libs/context_providers/highlighted_code_context_provider.py new file mode 100644 index 00000000..23d4fc86 --- /dev/null +++ b/continuedev/src/continuedev/libs/context_providers/highlighted_code_context_provider.py @@ -0,0 +1,191 @@ +import os +from typing import Any, Dict, List + +import meilisearch +from ...core.main import ChatMessage +from ...models.filesystem import RangeInFile, RangeInFileWithContents +from ...core.context import ContextItem, ContextItemDescription, ContextItemId +from pydantic import BaseModel + + +class HighlightedRangeContextItem(BaseModel): +    rif: RangeInFileWithContents +    item: ContextItem + + +class HighlightedCodeContextProvider(BaseModel): +    """ +    The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. +    When you type '@', the context provider will be asked to populate a list of options. +    These options will be updated on each keystroke. +    When you hit enter on an option, the context provider will add that item to the autopilot's list of context (which is all stored in the ContextManager object). +    """ + +    title = "code" + +    ide: Any  # IdeProtocolServer + +    highlighted_ranges: List[HighlightedRangeContextItem] = [] +    adding_highlighted_code: bool = False + +    should_get_fallback_context_item: bool = True +    last_added_fallback: bool = False + +    async def _get_fallback_context_item(self) -> HighlightedRangeContextItem: +        if not self.should_get_fallback_context_item: +            return None + +        visible_files = await self.ide.getVisibleFiles() +        if len(visible_files) > 0: +            content = await self.ide.readFile(visible_files[0]) +            rif = RangeInFileWithContents.from_entire_file( +                visible_files[0], content) + +            item = self._rif_to_context_item(rif, 0, True) +            item.description.name = self._rif_to_name( +                rif, show_line_nums=False) + +            self.last_added_fallback = True +            return HighlightedRangeContextItem(rif=rif, item=item) + +        return None + +    async def get_selected_items(self) -> List[ContextItem]: +        items = [hr.item for hr in self.highlighted_ranges] + +        if len(items) == 0 and (fallback_item := await self._get_fallback_context_item()): +            items = [fallback_item.item] + +        return items + +    async def get_chat_messages(self) -> List[ContextItem]: +        ranges = self.highlighted_ranges +        if len(ranges) == 0 and (fallback_item := await self._get_fallback_context_item()): +            ranges = [fallback_item] + +        return [ChatMessage( +            role="user", +            content=f"Code in this file is highlighted ({r.rif.filepath}):\n```\n{r.rif.contents}\n```", +            summary=f"Code in this file is highlighted: {r.rif.filepath}" +        ) for r in ranges] + +    def _make_sure_is_editing_range(self): +        """If none of the highlighted ranges are currently being edited, the first should be selected""" +        if len(self.highlighted_ranges) == 0: +            return +        if not any(map(lambda x: x.item.editing, self.highlighted_ranges)): +            self.highlighted_ranges[0].item.editing = True + +    def _disambiguate_highlighted_ranges(self): +        """If any files have the same name, also display their folder name""" +        name_status: Dict[str, set] = { +        }  # basename -> set of full paths with that basename +        for hr in self.highlighted_ranges: +            basename = os.path.basename(hr.rif.filepath) +            if basename in name_status: +                name_status[basename].add(hr.rif.filepath) +            else: +                name_status[basename] = {hr.rif.filepath} + +        for hr in self.highlighted_ranges: +            if len(name_status[basename]) > 1: +                hr.item.description.name = self._rif_to_name(hr.rif, display_filename=os.path.join( +                    os.path.basename(os.path.dirname(hr.rif.filepath)), basename)) +            else: +                hr.item.description.name = self._rif_to_name( +                    hr.rif, display_filename=basename) + +    async def provide_context_items(self) -> List[ContextItem]: +        return [] + +    async def delete_context_with_ids(self, ids: List[ContextItemId]) -> List[ContextItem]: +        indices_to_delete = [ +            int(id.item_id) for id in ids +        ] + +        kept_ranges = [] +        for i, hr in enumerate(self.highlighted_ranges): +            if i not in indices_to_delete: +                kept_ranges.append(hr) +        self.highlighted_ranges = kept_ranges + +        self._make_sure_is_editing_range() + +        if len(self.highlighted_ranges) == 0 and self.last_added_fallback: +            self.should_get_fallback_context_item = False + +        return [hr.item for hr in self.highlighted_ranges] + +    def _rif_to_name(self, rif: RangeInFileWithContents, display_filename: str = None, show_line_nums: bool = True) -> str: +        line_nums = f" ({rif.range.start.line + 1}-{rif.range.end.line + 1})" if show_line_nums else "" +        return f"{display_filename or os.path.basename(rif.filepath)}{line_nums}" + +    def _rif_to_context_item(self, rif: RangeInFileWithContents, idx: int, editing: bool) -> ContextItem: +        return ContextItem( +            description=ContextItemDescription( +                name=self._rif_to_name(rif), +                description=rif.filepath, +                id=ContextItemId( +                    provider_title=self.title, +                    item_id=str(idx) +                ) +            ), +            content=rif.contents, +            editing=editing, +            editable=True +        ) + +    async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]): +        self.should_get_fallback_context_item = True +        self.last_added_fallback = False + +        # Filter out rifs from ~/.continue/diffs folder +        range_in_files = [ +            rif for rif in range_in_files if not os.path.dirname(rif.filepath) == os.path.expanduser("~/.continue/diffs")] + +        # If not adding highlighted code +        if not self.adding_highlighted_code: +            if len(self.highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end): +                # If un-highlighting the range to edit, then remove the range +                self.highlighted_ranges = [] +            elif len(range_in_files) > 0: +                # Otherwise, replace the current range with the new one +                # This is the first range to be highlighted +                self.highlighted_ranges = [ +                    HighlightedRangeContextItem( +                        rif=range_in_files[0], +                        item=self._rif_to_context_item(range_in_files[0], 0, True))] + +            return + +        # If current range overlaps with any others, delete them and only keep the new range +        new_ranges = [] +        for i, hr in enumerate(self.highlighted_ranges): +            found_overlap = False +            for new_rif in range_in_files: +                if hr.rif.filepath == new_rif.filepath and hr.rif.range.overlaps_with(new_rif.range): +                    found_overlap = True +                    break + +                # Also don't allow multiple ranges in same file with same content. This is useless to the model, and avoids +                # the bug where cmd+f causes repeated highlights +                if hr.rif.filepath == new_rif.filepath and hr.rif.contents == new_rif.contents: +                    found_overlap = True +                    break + +            if not found_overlap: +                new_ranges.append(HighlightedRangeContextItem(rif=hr.rif, item=self._rif_to_context_item( +                    hr.rif, len(new_ranges), False))) + +        self.highlighted_ranges = new_ranges + [HighlightedRangeContextItem(rif=rif, item=self._rif_to_context_item( +            rif, len(new_ranges) + idx, False)) for idx, rif in enumerate(range_in_files)] + +        self._make_sure_is_editing_range() +        self._disambiguate_highlighted_ranges() + +    async def set_editing_at_ids(self, ids: List[str]): +        for hr in self.highlighted_ranges: +            hr.item.editing = hr.item.description.id.to_string() in ids + +    async def add_context_item(self, id: ContextItemId, query: str, search_client: meilisearch.Client, prev: List[ContextItem] = None) -> List[ContextItem]: +        raise NotImplementedError() diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py index fddef887..d6ce13b3 100644 --- a/continuedev/src/continuedev/libs/util/paths.py +++ b/continuedev/src/continuedev/libs/util/paths.py @@ -2,16 +2,26 @@ import os  from ..constants.main import CONTINUE_SESSIONS_FOLDER, CONTINUE_GLOBAL_FOLDER, CONTINUE_SERVER_FOLDER -def getGlobalFolderPath(): -    return os.path.join(os.path.expanduser("~"), CONTINUE_GLOBAL_FOLDER) +def getGlobalFolderPath(): +    path = os.path.join(os.path.expanduser("~"), CONTINUE_GLOBAL_FOLDER) +    os.makedirs(path, exist_ok=True) +    return path  def getSessionsFolderPath(): -    return os.path.join(getGlobalFolderPath(), CONTINUE_SESSIONS_FOLDER) +    path = os.path.join(getGlobalFolderPath(), CONTINUE_SESSIONS_FOLDER) +    os.makedirs(path, exist_ok=True) +    return path +  def getServerFolderPath(): -    return os.path.join(getGlobalFolderPath(), CONTINUE_SERVER_FOLDER) +    path = os.path.join(getGlobalFolderPath(), CONTINUE_SERVER_FOLDER) +    os.makedirs(path, exist_ok=True) +    return path +  def getSessionFilePath(session_id: str): -    return os.path.join(getSessionsFolderPath(), f"{session_id}.json")
\ No newline at end of file +    path = os.path.join(getSessionsFolderPath(), f"{session_id}.json") +    os.makedirs(os.path.dirname(path), exist_ok=True) +    return path diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index 6cebf429..06614984 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -2,6 +2,7 @@ from .main import *  from .filesystem import RangeInFile, FileEdit  from .filesystem_edit import FileEditWithFullContents  from ..core.main import History, HistoryNode, FullState +from ..core.context import ContextItem  from pydantic import schema_json_of  import os @@ -13,6 +14,8 @@ MODELS_TO_GENERATE = [      FileEditWithFullContents  ] + [      History, HistoryNode, FullState +] + [ +    ContextItem  ]  RENAMES = { diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index ae57c0b6..36b2f3fa 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -91,25 +91,19 @@ class GUIProtocolServer(AbstractGUIProtocolServer):                  self.on_clear_history()              elif message_type == "delete_at_index":                  self.on_delete_at_index(data["index"]) -            elif message_type == "delete_context_at_indices": -                self.on_delete_context_at_indices(data["indices"]) +            elif message_type == "delete_context_with_ids": +                self.on_delete_context_with_ids(data["ids"])              elif message_type == "toggle_adding_highlighted_code":                  self.on_toggle_adding_highlighted_code()              elif message_type == "set_editing_at_indices":                  self.on_set_editing_at_indices(data["indices"]) -            elif message_type == "set_pinned_at_indices": -                self.on_set_pinned_at_indices(data["indices"])              elif message_type == "show_logs_at_index":                  self.on_show_logs_at_index(data["index"]) +            elif message_type == "select_context_item": +                self.select_context_item(data["id"], data["query"])          except Exception as e:              print(e) -    async def send_state_update(self): -        state = self.session.autopilot.get_full_state().dict() -        await self._send_json("state_update", { -            "state": state -        }) -      def on_main_input(self, input: str):          # Do something with user input          create_async_task(self.session.autopilot.accept_user_input( @@ -144,10 +138,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer):          create_async_task(self.session.autopilot.delete_at_index(              index), self.session.autopilot.continue_sdk.ide.unique_id) -    def on_delete_context_at_indices(self, indices: List[int]): +    def on_delete_context_with_ids(self, ids: List[str]):          create_async_task( -            self.session.autopilot.delete_context_at_indices( -                indices), self.session.autopilot.continue_sdk.ide.unique_id +            self.session.autopilot.delete_context_with_ids( +                ids), self.session.autopilot.continue_sdk.ide.unique_id          )      def on_toggle_adding_highlighted_code(self): @@ -162,18 +156,17 @@ class GUIProtocolServer(AbstractGUIProtocolServer):                  indices), self.session.autopilot.continue_sdk.ide.unique_id          ) -    def on_set_pinned_at_indices(self, indices: List[int]): -        create_async_task( -            self.session.autopilot.set_pinned_at_indices( -                indices), self.session.autopilot.continue_sdk.ide.unique_id -        ) -      def on_show_logs_at_index(self, index: int):          name = f"continue_logs.txt"          logs = "\n\n############################################\n\n".join(              ["This is a log of the exact prompt/completion pairs sent/received from the LLM during this step"] + self.session.autopilot.continue_sdk.history.timeline[index].logs)          create_async_task( -            self.session.autopilot.ide.showVirtualFile(name, logs)) +            self.session.autopilot.ide.showVirtualFile(name, logs), self.session.autopilot.continue_sdk.ide.unique_id) + +    def select_context_item(self, id: str, query: str): +        """Called when user selects an item from the dropdown""" +        create_async_task( +            self.session.autopilot.select_context_item(id, query), self.session.autopilot.continue_sdk.ide.unique_id)  @router.websocket("/ws") @@ -188,7 +181,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we          protocol.websocket = websocket          # Update any history that may have happened before connection -        await protocol.send_state_update() +        await protocol.session.autopilot.update_subscribers()          while AppStatus.should_exit is False:              message = await websocket.receive_text() @@ -214,5 +207,5 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we          if websocket.client_state != WebSocketState.DISCONNECTED:              await websocket.close() -        session_manager.persist_session(session.session_id) +        await session_manager.persist_session(session.session_id)          session_manager.remove_session(session.session_id) diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index 9766fcd0..fb230216 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -1,6 +1,8 @@  from typing import Any, Dict, List  from abc import ABC, abstractmethod +from ..core.context import ContextItem +  class AbstractGUIProtocolServer(ABC):      @abstractmethod @@ -24,10 +26,6 @@ class AbstractGUIProtocolServer(ABC):          """Called when the user inputs a step"""      @abstractmethod -    async def send_state_update(self, state: dict): -        """Send a state update to the client""" - -    @abstractmethod      def on_retry_at_index(self, index: int):          """Called when the user requests a retry at a previous index""" @@ -42,3 +40,7 @@ class AbstractGUIProtocolServer(ABC):      @abstractmethod      def on_delete_at_index(self, index: int):          """Called when the user requests to delete a step at a given index""" + +    @abstractmethod +    def select_context_item(self, id: str, query: str): +        """Called when user selects an item from the dropdown""" diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 42dc0cc1..7ee64041 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -1,15 +1,20 @@ +import asyncio +import subprocess  import time +import meilisearch  import psutil  import os  from fastapi import FastAPI  from fastapi.middleware.cors import CORSMiddleware -from .ide import router as ide_router -from .gui import router as gui_router -from .session_manager import session_manager  import atexit  import uvicorn  import argparse +from .ide import router as ide_router +from .gui import router as gui_router +from .session_manager import session_manager +from .meilisearch_server import start_meilisearch +  app = FastAPI()  app.include_router(ide_router) @@ -41,15 +46,20 @@ args = parser.parse_args()  # log_file = open('output.log', 'a')  # sys.stdout = log_file -  def run_server():      uvicorn.run(app, host="0.0.0.0", port=args.port) -def cleanup(): +async def cleanup_coroutine():      print("Cleaning up sessions")      for session_id in session_manager.sessions: -        session_manager.persist_session(session_id) +        await session_manager.persist_session(session_id) + + +def cleanup(): +    loop = asyncio.new_event_loop() +    loop.run_until_complete(cleanup_coroutine()) +    loop.close()  def cpu_usage_report(): @@ -77,6 +87,12 @@ if __name__ == "__main__":          # cpu_thread = threading.Thread(target=cpu_usage_loop)          # cpu_thread.start() +        try: +            start_meilisearch() +        except Exception as e: +            print("Failed to start MeiliSearch") +            print(e) +          run_server()      except Exception as e:          cleanup() diff --git a/continuedev/src/continuedev/server/meilisearch_server.py b/continuedev/src/continuedev/server/meilisearch_server.py new file mode 100644 index 00000000..419f081f --- /dev/null +++ b/continuedev/src/continuedev/server/meilisearch_server.py @@ -0,0 +1,56 @@ +import os +import subprocess + +import meilisearch +from ..libs.util.paths import getServerFolderPath + + +def check_meilisearch_installed() -> bool: +    """ +    Checks if MeiliSearch is installed. +    """ + +    serverPath = getServerFolderPath() +    meilisearchPath = os.path.join(serverPath, "meilisearch") + +    return os.path.exists(meilisearchPath) + + +def check_meilisearch_running() -> bool: +    """ +    Checks if MeiliSearch is running. +    """ + +    try: +        client = meilisearch.Client('http://localhost:7700') +        resp = client.health() +        if resp["status"] != "available": +            return False +        return True +    except Exception: +        return False + + +def start_meilisearch(): +    """ +    Starts the MeiliSearch server, wait for it. +    """ + +    # Doesn't work on windows for now +    if not os.name == "posix": +        return + +    serverPath = getServerFolderPath() + +    # Check if MeiliSearch is installed +    if not check_meilisearch_installed(): +        # Download MeiliSearch +        print("Downloading MeiliSearch...") +        subprocess.run( +            f"curl -L https://install.meilisearch.com | sh", shell=True, check=True, cwd=serverPath) + +    # Check if MeiliSearch is running +    if not check_meilisearch_running(): +        print("Starting MeiliSearch...") +        subprocess.Popen(["./meilisearch"], cwd=serverPath, stdout=subprocess.DEVNULL, +                         stderr=subprocess.STDOUT, close_fds=True, start_new_session=True) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 90172a4e..96daf92c 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -74,7 +74,7 @@ class SessionManager:          async def on_update(state: FullState):              await session_manager.send_ws_data(session_id, "state_update", { -                "state": autopilot.get_full_state().dict() +                "state": state.dict()              })          autopilot.on_update(on_update) @@ -84,9 +84,9 @@ class SessionManager:      def remove_session(self, session_id: str):          del self.sessions[session_id] -    def persist_session(self, session_id: str): +    async def persist_session(self, session_id: str):          """Save the session's FullState as a json file""" -        full_state = self.sessions[session_id].autopilot.get_full_state() +        full_state = await self.sessions[session_id].autopilot.get_full_state()          if not os.path.exists(getSessionsFolderPath()):              os.mkdir(getSessionsFolderPath())          with open(getSessionFilePath(session_id), "w") as f:  | 
