From 2e96200efc317c5e448874473e3b842ca4a04674 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:54:12 +0200 Subject: [PATCH 1/5] chore: bump e2b to 2.38.3 (js) and 2.38.0 (python) Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/shaggy-plums-wave.md | 6 ++ js/package.json | 2 +- pnpm-lock.yaml | 103 +++++++++++--------- python/poetry.lock | 162 ++++++++++++++++++++++++++++---- python/pyproject.toml | 2 +- 5 files changed, 207 insertions(+), 68 deletions(-) create mode 100644 .changeset/shaggy-plums-wave.md diff --git a/.changeset/shaggy-plums-wave.md b/.changeset/shaggy-plums-wave.md new file mode 100644 index 00000000..7b6084c2 --- /dev/null +++ b/.changeset/shaggy-plums-wave.md @@ -0,0 +1,6 @@ +--- +'@e2b/code-interpreter': patch +'@e2b/code-interpreter-python': patch +--- + +Bump E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0 diff --git a/js/package.json b/js/package.json index 158da8e5..6493fe9e 100644 --- a/js/package.json +++ b/js/package.json @@ -71,6 +71,6 @@ "defaults" ], "dependencies": { - "e2b": "^2.28.0" + "e2b": "^2.38.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3fd9f94c..4b479787 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: js: dependencies: e2b: - specifier: ^2.28.0 - version: 2.33.0 + specifier: ^2.38.3 + version: 2.38.3 devDependencies: '@types/node': specifier: ^20.19.19 @@ -317,10 +317,6 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -354,6 +350,7 @@ packages: engines: {node: ^22.20 || ^24.12 || >=25} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} @@ -412,41 +409,49 @@ packages: resolution: {integrity: sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.20.0': resolution: {integrity: sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': resolution: {integrity: sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': resolution: {integrity: sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': resolution: {integrity: sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': resolution: {integrity: sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.20.0': resolution: {integrity: sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.20.0': resolution: {integrity: sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.20.0': resolution: {integrity: sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==} @@ -532,121 +537,145 @@ packages: resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-gnueabihf@4.62.4': resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.39.0': resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm-musleabihf@4.62.4': resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.39.0': resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-gnu@4.62.4': resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.39.0': resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-musl@4.62.4': resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.62.4': resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.62.4': resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.39.0': resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.62.4': resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.62.4': resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.39.0': resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.62.4': resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.39.0': resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-musl@4.62.4': resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.39.0': resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.62.4': resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.39.0': resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.4': resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.39.0': resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-linux-x64-musl@4.62.4': resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.62.4': resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} @@ -1054,8 +1083,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - e2b@2.33.0: - resolution: {integrity: sha512-n6W0nsJMetz8m30sLPMYfliPTW0VpuaDTUOjB0ZdYZ87li4zyX0UQqeQ5S/YPvq8EX9zEYcy3SijBxn1FGFlkQ==} + e2b@2.38.3: + resolution: {integrity: sha512-0tOokTvlltpYGO4UAMNcoSSkFEn2VHAGaJtZsMVhPf5GMmdE9RNcfla/KwCpaE8ef7cFKpSXCs+UUDyE/r/fMQ==} engines: {node: '>=20.18.1 <21 || >=22'} es-module-lexer@2.3.0: @@ -1187,10 +1216,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - formatly@0.3.0: resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} engines: {node: '>=18.3.0'} @@ -1220,11 +1245,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -1294,10 +1317,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1482,9 +1501,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1794,10 +1810,14 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@7.28.0: - resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -2111,8 +2131,6 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@isaacs/cliui@9.0.0': {} - '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -2764,7 +2782,7 @@ snapshots: dotenv@16.6.1: {} - e2b@2.33.0: + e2b@2.38.3: dependencies: '@bufbuild/protobuf': 2.12.1 '@connectrpc/connect': 2.1.2(@bufbuild/protobuf@2.12.1) @@ -2772,11 +2790,13 @@ snapshots: chalk: 5.6.2 compare-versions: 6.1.1 dockerfile-ast: 0.7.1 - glob: 11.1.0 + glob: 13.0.6 openapi-fetch: 0.14.1 platform: 1.3.6 tar: 7.5.20 - undici: 7.28.0 + undici: 7.29.0 + optionalDependencies: + undici8: undici@8.10.0 es-module-lexer@2.3.0: {} @@ -2956,11 +2976,6 @@ snapshots: flatted@3.3.3: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - formatly@0.3.0: dependencies: fd-package-json: 2.0.0 @@ -2990,13 +3005,10 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@11.1.0: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.2.3 minimatch: 10.2.5 minipass: 7.1.3 - package-json-from-dist: 1.0.1 path-scurry: 2.0.2 glob@7.2.3: @@ -3061,10 +3073,6 @@ snapshots: isexe@2.0.0: {} - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - jiti@2.7.0: {} joycon@3.1.1: {} @@ -3262,8 +3270,6 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3560,7 +3566,10 @@ snapshots: undici-types@6.21.0: {} - undici@7.28.0: {} + undici@7.29.0: {} + + undici@8.10.0: + optional: true universalify@0.1.2: {} diff --git a/python/poetry.lock b/python/poetry.lock index d834dc40..4a6548fc 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -82,6 +82,22 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "connectrpc" +version = "0.11.1" +description = "Server and client runtime library for Connect RPC" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "connectrpc-0.11.1-py3-none-any.whl", hash = "sha256:8a52e2e92a485fa9681c1101a79a5ebeb31807e3ea3d5aabd41f484a6398bc7b"}, + {file = "connectrpc-0.11.1.tar.gz", hash = "sha256:18277f7838847b4271ca38d40c7d2387b5a2ea6a29f240689c19e1ec84aaff66"}, +] + +[package.dependencies] +protobuf-py = "0.1.1" +pyqwest = ">=0.5.1" + [[package]] name = "contourpy" version = "1.3.2" @@ -189,28 +205,29 @@ files = [ [[package]] name = "e2b" -version = "2.32.0" +version = "2.38.0" description = "E2B SDK that give agents cloud environments" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "e2b-2.32.0-py3-none-any.whl", hash = "sha256:0f1971f8e287aa717ad3e44c2cbe26753da97acf34da24225b07675a07c57300"}, - {file = "e2b-2.32.0.tar.gz", hash = "sha256:40694567e87c1060e6e7625bda381e739186b32e5c5617deccfb925fb1101167"}, + {file = "e2b-2.38.0-py3-none-any.whl", hash = "sha256:9f43803aa23e9a33ed48c1c778d588f83c0390db21edf3098255137981541b67"}, + {file = "e2b-2.38.0.tar.gz", hash = "sha256:b245976cc946d144baf77341b19fa537bfd7cbd9028dc8ef59bd6947f5e329c1"}, ] [package.dependencies] attrs = ">=23.2.0" +connectrpc = ">=0.11.1,<0.12" dockerfile-parse = ">=2.0.1,<3" h2 = ">=4,<5" -httpcore = ">=1.0.5,<2" httpx = ">=0.27.0,<1.0.0" packaging = ">=24.1" -protobuf = ">=4.21.0" +protobuf-py = ">=0.1.1,<0.2" +pyqwest = ">=0.9.0,<0.10" python-dateutil = ">=2.8.2" rich = ">=14.0.0" -typing-extensions = ">=4.1.0" -wcmatch = ">=10.1,<11" +typing-extensions = ">=4.10.0" +wcmatch = ">=10.1,<12" [[package]] name = "exceptiongroup" @@ -752,6 +769,21 @@ files = [ {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, ] +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef"}, + {file = "opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a"}, +] + +[package.dependencies] +typing-extensions = ">=4.5.0" + [[package]] name = "packaging" version = "26.2" @@ -886,21 +918,57 @@ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] -name = "protobuf" -version = "7.35.0" -description = "" +name = "protobuf-py" +version = "0.1.1" +description = "Idiomatic Protocol Buffers for Python." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda"}, - {file = "protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5"}, - {file = "protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee"}, - {file = "protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011"}, - {file = "protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6"}, - {file = "protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201"}, - {file = "protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0"}, - {file = "protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6"}, + {file = "protobuf_py-0.1.1-py3-none-any.whl", hash = "sha256:efc4f50f275ed6dae10a1f30bb81ad1a75368557b3ff22a532b7a472050368f1"}, + {file = "protobuf_py-0.1.1.tar.gz", hash = "sha256:6bd08ac4d8f1661965bbe2685429d79043704cdd1ee720a7a89617331742240b"}, +] + +[package.dependencies] +protobuf-py-ext = {version = "0.1.1", markers = "(platform_machine == \"AMD64\" or platform_machine == \"ARM64\") and platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or (platform_machine == \"aarch64\" or platform_machine == \"x86_64\") and platform_python_implementation == \"CPython\" and sys_platform == \"linux\" or platform_machine == \"arm64\" and platform_python_implementation == \"CPython\" and sys_platform == \"darwin\""} + +[[package]] +name = "protobuf-py-ext" +version = "0.1.1" +description = "Native extension for protobuf-py" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "(platform_machine == \"AMD64\" or platform_machine == \"ARM64\") and platform_python_implementation == \"CPython\" and sys_platform == \"win32\" or (platform_machine == \"aarch64\" or platform_machine == \"x86_64\") and platform_python_implementation == \"CPython\" and sys_platform == \"linux\" or platform_machine == \"arm64\" and platform_python_implementation == \"CPython\" and sys_platform == \"darwin\"" +files = [ + {file = "protobuf_py_ext-0.1.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:310039e03cb15181781a0b78017419f6d4ee302e988c3c70b87f1facdf05532d"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ec8348d1ba045f79b2fedd14e40cca36f2a41b52f2c4fdf55a60c58add2353"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:182798e4861aba72d05855bd06febe4926aa7265e6f444a1b8af5252beee4f7e"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9eb25c3a329c0551cc86b209a5e5d8ecb8d834b9924a3aa019377853a703b6d3"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aaecbde82bef10c7c40578cbb61b7a19896bf7fa450972050a3bb302acb7d5d6"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-win_amd64.whl", hash = "sha256:6b0c615c48e95acc53cf33e9310eeaff8b30d2d7555bf93e7bca8fb4f40e9a5c"}, + {file = "protobuf_py_ext-0.1.1-cp310-abi3-win_arm64.whl", hash = "sha256:72956cd0af5dee24b41c6f5ba5e42622d17e6d555002b5efc1634e27a1446de2"}, + {file = "protobuf_py_ext-0.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f0cd58620f415a3534d195358338f4999a774e12510f91c592b38d13d37388"}, + {file = "protobuf_py_ext-0.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21572764f625d829604fc4d83635f533f35775f11c6da142345b3f3c8d64bd09"}, + {file = "protobuf_py_ext-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7f14f00c2678bfbe7ad46f057b9f9938c1677bcf39e405e163e272c6f9814b8"}, + {file = "protobuf_py_ext-0.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1610865622e2e27568277ca63d8d2d23dcc55eaa767398865c21539ddf4ce24d"}, + {file = "protobuf_py_ext-0.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8995efba9476e1ea18ef9873306871dba697308994bc2766558fec3387acc3de"}, + {file = "protobuf_py_ext-0.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba61d49dace8f874361583030a7c48139b42eb37c9ffbb1e7e8a227a51576f44"}, + {file = "protobuf_py_ext-0.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a9c2f026096ca4c595c89a297067ef371e241d3ff8e1f6d7c779aa8419cdca7"}, + {file = "protobuf_py_ext-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf78707a040b9294e5e1ec4a1875f0046acfe52e92150cea27de4e9fc9db39bb"}, + {file = "protobuf_py_ext-0.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ae4373845cbb85bdade3ef5368cc2f4b5f80bf173383afc1ab063f95644e5599"}, + {file = "protobuf_py_ext-0.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2a4cc478eef7a2acc1daaebcde479a3ac2396d47e6bdc7e776ca4c4147ba8b4c"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:359dccdc1c3eafed2a913c570bb082b8df848a1d27d548ce8385f246a7d68be2"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c38b4a10a306366443273ee03ca554537a0965bf05b7ada8e7dbdee04cc93e"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79eee3bcbb289d6ea114eb8fe3a1469c5b59bf53e207238469f567d9c53ba56f"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:10992141a8282a71ac3e3530d1a489efb27618d84000b9a47918cf70e5816d9b"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4411ffe0e06a774b83c5c71c546ce097640a25f596c45f95f53d3e3148e3f22d"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55a17d80ea419501ff221a6627523f38a431bb33e6aa3de81ae3a7f271c49c75"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbb518b5638403ceaa08ac4fc7dac626f45ed9b856b3517de3906cf3de4d632"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a4adc65ab6a5e4885c67fc808bcacb83d755d05b566d312a0e10c2a873f2ad4"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e97f45c49676efacfb8e95bfcb1a002bc337e618a6780be1e55df2ba5ebd2f1e"}, + {file = "protobuf_py_ext-0.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:53a6b3590f6aa7f97b8ed60f62fef9b096babfeae82f7283fd3a4c405827d4f8"}, + {file = "protobuf_py_ext-0.1.1.tar.gz", hash = "sha256:e85bfdfdb3ed50634db8ccc7429dd9286520109489c735463971a418707b4fef"}, ] [[package]] @@ -933,6 +1001,62 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyqwest" +version = "0.9.0" +description = "A modern, high-performance HTTP client for Python and Rust." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pyqwest-0.9.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:517d2eb295d56ef1530cebe0cf290a66fa199c57955bd1c5f1dc592240fbb7d6"}, + {file = "pyqwest-0.9.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:408dc692835363b6824bee61ee9e4c9a5bba9c08d75b903f82f9a802e41d1b30"}, + {file = "pyqwest-0.9.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1abf1ab3a0c1b545651ab37a643acc7909c4e7644a63a2e4af36e3e230a0db5"}, + {file = "pyqwest-0.9.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b02e5c9ce57e079eb3716c464d3818ad2573d7743483c39526ad77ee95af806"}, + {file = "pyqwest-0.9.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:98bdaa30a3a8d8c71506a33c084e485a243fbf51e77130bb39e0f249dd116142"}, + {file = "pyqwest-0.9.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:10eba84f9d8512d23bd16dd60b1c77dab956470361559214981dda065df4ad1a"}, + {file = "pyqwest-0.9.0-cp310-abi3-win_amd64.whl", hash = "sha256:af4e859800b02cd1fcdd898af26a881c4a8ce9520f71f6cb0a5403523d2cbbdf"}, + {file = "pyqwest-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:abe322cc1b63947b493bc06028615e0d5de655928159227aab64f60a0eb0e27d"}, + {file = "pyqwest-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6b2dc6a9c583859d031941e1041b8e711afe0ad4c130628207731cef2b1a486"}, + {file = "pyqwest-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:551e47714da72a72939958029eb37a754cecd974344471c4a2038583a66a5396"}, + {file = "pyqwest-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3be3d38ccdab3077bf1cfe90c346ce927f9b49c56dcdf20e060f5f2502d01021"}, + {file = "pyqwest-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:45d9d424da878b22604799d1ce2a0a5df054924487ee5a851ad08025e2076e5c"}, + {file = "pyqwest-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4420adcd2b756da706b31a04f095a521f6e440dab284b7a1b46c76e048c1f78"}, + {file = "pyqwest-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:25734bd9798bbfaa53c83d395649dd9ea9a791a64b9848734955cb78ad52c832"}, + {file = "pyqwest-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6e4c2afe6c7293d9e42cfdb0489b0b5f64002159c2dc9c45d9322c1b4ffb42cd"}, + {file = "pyqwest-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:67f153b94dfbb9aeabc38f20a1bada1acfd9361bec12db86268bd951be35a154"}, + {file = "pyqwest-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9942ffa243c8c2243e3ce04e3e3ec9b4c119346c963fd0051cd989f0defecf6"}, + {file = "pyqwest-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f8f9c880c19826fead1838e12b042865b15df3ec844928cc74188a8b740f761"}, + {file = "pyqwest-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:56040761f6ca7abb036c7d288863bf143a9b166d824d98a604908ef20c6906e0"}, + {file = "pyqwest-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:02a51df744521e45eb5379bc325b91de4512ec64a9f4aba3248f9166ec4e5b88"}, + {file = "pyqwest-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9a0e3123aee446d5f6e57c43cae87b4dab4b1af3fa970d114d74d5d4b9d075c"}, + {file = "pyqwest-0.9.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:fdab21bbedff4a21209135423f5f54f287a01dd6143cbd7aeab63d5d8c889654"}, + {file = "pyqwest-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:21a04ddf4497ef3c7fe36cdc473c094999b7710005d38bd8a0dfd4a3feace12f"}, + {file = "pyqwest-0.9.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8c51c1a27ea529fffab90c127a045f0dfe1abc54d5b9ad5f4165e954439c9b2"}, + {file = "pyqwest-0.9.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f283ffc40a2774e0dc8fbb47e4a8f9f8a698a99feae3bf4ad75b340c98031b"}, + {file = "pyqwest-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f73d4a8e75b5860741fbd0623a0ce792a53336dbfec5f34736004acd5446843"}, + {file = "pyqwest-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e34d55a53492b44b82b690c2b11957fc35e99ebab4db2e9bcb2a1e2621d71b62"}, + {file = "pyqwest-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:48c367150783f6866d0af43c209cc405f8287743d7b2472c7d757fda2478fd57"}, + {file = "pyqwest-0.9.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10fae206270355c993b49c49bf9778995167f5efe591807d50a8e682921d71ea"}, + {file = "pyqwest-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5f475129fb792593222a9ce0fefbbc10bda1544a7411f3946d771c9a61c50e61"}, + {file = "pyqwest-0.9.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78f6f676d349535cb094e8e3bcf33863238a142fb7dcc7afb9e61749e2351047"}, + {file = "pyqwest-0.9.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f460f5fcdae8cc46e6a679128836f6cd47fbf4bf73e5c9d6ea868e4a90b2e7ee"}, + {file = "pyqwest-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cba6804151e973d27c99b9f02b3f0bcab7270c3f99725b508ada9c147365cabd"}, + {file = "pyqwest-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bc9dfeca326918d70a5368114de0fc627d9f64fafa36563ab484ae58ffe42c0d"}, + {file = "pyqwest-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384693adc0908f16324e5b508a3c25fb9a92b88f9bdf7ccb6820507db4a95888"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c174d8ebde256c457f455dcdedf59446528d84874cb5967041be0d1bda05929"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d5e56192b03bca7ec511c9af4ed80fa10fc1c1871d0ccfe49050632785b46099"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3fa3c93638fc645c7c626fe83032f53972673581878bd85702842f41665ad6c"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869d1c630cbff38510207c5b57139f319acca21ed978f9ce6648fcd868df5fbd"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a0bc2ba9b6d3103c6f0cce59cca9e2e294d1707bca435b471a1917db73010801"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:bfb04e3e91166fe6e4eecdcfccb0b2490da7629984b7988cfd4b11daa1dbb616"}, + {file = "pyqwest-0.9.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fbff11bbffb572c084ccd76048c40bd57976b255d34f59280e56626f556cfaeb"}, + {file = "pyqwest-0.9.0.tar.gz", hash = "sha256:514dd0b37d7a1bcb978b5d6423d15f89cf7dcf8058ac2a37aa42cd30090de89e"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.39.1" + [[package]] name = "pytest" version = "9.0.3" @@ -1193,4 +1317,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "4b9fdae45121bdfe70eafce86abac886c8379200b6689d0d99a24aef87b152d7" +content-hash = "ce4d7b40fe317581c81fae1d8ca0279d144b21fe48bb67298f982e4956c3b060" diff --git a/python/pyproject.toml b/python/pyproject.toml index 343543d7..919de9ed 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,7 +14,7 @@ python = "^3.10" httpx = ">=0.20.0, <1.0.0" attrs = ">=21.3.0" -e2b = "^2.26.0" +e2b = "^2.38.0" [tool.poetry.group.dev.dependencies] pytest = "^9.0.3" From d268be90333cf8d046f041c30e8b180bb8867424 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:42:53 +0200 Subject: [PATCH 2/5] fix(python): build own HTTP/1.1 transport for Jupyter requests e2b 2.38.0 moved the Python SDK's HTTP stack onto pyqwest, dropping the `http2` argument from the internal `get_transport()` helper. Passing it raised `TypeError` on every `_client` access, and simply dropping it would have left Jupyter requests on ALPN-negotiated HTTP/2, where a cancelled request only resets the stream and the server never sees the disconnect. Build the transport in `e2b_code_interpreter.transport` instead, with `http_version` pinned to HTTP/1.1 and the SDK's pool tuning and connect-only retry policy, so client disconnects still arrive as a TCP close and long-running executions stay cancellable. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/shaggy-plums-wave.md | 4 +- .../code_interpreter_async.py | 22 ++--- .../code_interpreter_sync.py | 21 ++--- python/e2b_code_interpreter/transport.py | 90 +++++++++++++++++++ 4 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 python/e2b_code_interpreter/transport.py diff --git a/.changeset/shaggy-plums-wave.md b/.changeset/shaggy-plums-wave.md index 7b6084c2..0fee642c 100644 --- a/.changeset/shaggy-plums-wave.md +++ b/.changeset/shaggy-plums-wave.md @@ -3,4 +3,6 @@ '@e2b/code-interpreter-python': patch --- -Bump E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0 +Bump the E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0. + +The Python SDK moved its HTTP stack onto [`pyqwest`](https://pypi.org/project/pyqwest/), and its internal `get_transport()` helper no longer takes an `http2` argument — the HTTP version is negotiated by ALPN instead, which means HTTP/2 against the sandbox. Jupyter requests build their own HTTP/1.1 transport now (`e2b_code_interpreter.transport`), from the same pool tuning and connect-only retry policy the SDK uses, so client disconnects keep propagating to the server as a TCP close and long-running executions stay reliably cancellable. diff --git a/python/e2b_code_interpreter/code_interpreter_async.py b/python/e2b_code_interpreter/code_interpreter_async.py index 82a58b51..e5ad107d 100644 --- a/python/e2b_code_interpreter/code_interpreter_async.py +++ b/python/e2b_code_interpreter/code_interpreter_async.py @@ -8,7 +8,7 @@ AsyncSandbox as BaseAsyncSandbox, InvalidArgumentException, ) -from e2b.api.client_async import get_transport +from e2b_code_interpreter.transport import get_async_transport from e2b_code_interpreter.constants import ( DEFAULT_TEMPLATE, @@ -70,23 +70,11 @@ def _jupyter_url(self) -> str: @property def _client(self) -> AsyncClient: - # TODO: Remove later - # Use a dedicated HTTP/1.1 transport for Jupyter requests. - # - # The base SDK's shared transport now defaults to http2=True. With - # HTTP/2, multiple requests are multiplexed over a single TCP - # connection, so when a client cancels a request (e.g. the caller - # disconnects from the streaming `/execute` endpoint) the server - # may not detect the disconnect: only the HTTP/2 stream is - # cancelled, the underlying TCP connection stays open. - # - # Forcing HTTP/1.1 here keeps the 1:1 mapping between TCP - # connection and request, so client disconnects propagate to the - # server as a TCP close and long-running executions can be - # cancelled reliably. The helper also caches the transport - # per-event-loop for async. + # Use a dedicated HTTP/1.1 transport for Jupyter requests so that + # client disconnects propagate to the server; see the module docstring + # of `e2b_code_interpreter.transport`. return AsyncClient( - transport=get_transport(self.connection_config, http2=False), + transport=get_async_transport(self.connection_config), ) async def _handle_connection_error(self, err: Exception) -> None: diff --git a/python/e2b_code_interpreter/code_interpreter_sync.py b/python/e2b_code_interpreter/code_interpreter_sync.py index b5ce8f2b..800e006b 100644 --- a/python/e2b_code_interpreter/code_interpreter_sync.py +++ b/python/e2b_code_interpreter/code_interpreter_sync.py @@ -4,7 +4,7 @@ from typing import Optional, Dict, overload, Union, List from httpx import Client from e2b import Sandbox as BaseSandbox, InvalidArgumentException -from e2b.api.client_sync import get_transport +from e2b_code_interpreter.transport import get_sync_transport from e2b_code_interpreter.constants import ( DEFAULT_TEMPLATE, @@ -67,21 +67,10 @@ def _jupyter_url(self) -> str: @property def _client(self) -> Client: - # TODO: Remove later - # Use a dedicated HTTP/1.1 transport for Jupyter requests. - # - # The base SDK's shared transport now defaults to http2=True. With - # HTTP/2, multiple requests are multiplexed over a single TCP - # connection, so when a client cancels a request (e.g. the caller - # disconnects from the streaming `/execute` endpoint) the server - # may not detect the disconnect: only the HTTP/2 stream is - # cancelled, the underlying TCP connection stays open. - # - # Forcing HTTP/1.1 here keeps the 1:1 mapping between TCP - # connection and request, so client disconnects propagate to the - # server as a TCP close and long-running executions can be - # cancelled reliably. - return Client(transport=get_transport(self.connection_config, http2=False)) + # Use a dedicated HTTP/1.1 transport for Jupyter requests so that + # client disconnects propagate to the server; see the module docstring + # of `e2b_code_interpreter.transport`. + return Client(transport=get_sync_transport(self.connection_config)) def _handle_connection_error(self, err: Exception) -> None: """ diff --git a/python/e2b_code_interpreter/transport.py b/python/e2b_code_interpreter/transport.py new file mode 100644 index 00000000..431132aa --- /dev/null +++ b/python/e2b_code_interpreter/transport.py @@ -0,0 +1,90 @@ +"""HTTP/1.1 transports for Jupyter requests. + +# TODO: Remove later +The base SDK's shared transports let ALPN negotiate the HTTP version, which +means HTTP/2 against the sandbox. With HTTP/2, multiple requests are +multiplexed over a single TCP connection, so when a client cancels a request +(e.g. the caller disconnects from the streaming `/execute` endpoint) the +server may not detect the disconnect: only the HTTP/2 stream is cancelled, +the underlying TCP connection stays open. + +Forcing HTTP/1.1 keeps the 1:1 mapping between TCP connection and request, so +client disconnects propagate to the server as a TCP close, uvicorn delivers +`http.disconnect`, and long-running executions can be cancelled reliably. + +The base SDK has no option for this, so we build the transport ourselves from +the same pieces it uses (its pool tuning and connect-only retry policy) with +`http_version` pinned to HTTP/1.1. +""" + +import threading +from typing import Dict, Optional + +from pyqwest import HTTPTransport, HTTPVersion, SyncHTTPTransport +from pyqwest.httpx import AsyncPyqwestTransport, PyqwestTransport + +from e2b.api import ( + ProxyConfig, + connection_retries, + pool_idle_timeout, + pool_max_idle_per_host, + proxy_to_config, +) +from e2b.api.client_async import ( + ConnectionRetryTransport as AsyncConnectionRetryTransport, +) +from e2b.api.client_sync import ConnectionRetryTransport as SyncConnectionRetryTransport +from e2b.connection_config import ConnectionConfig + +_transport_lock = threading.Lock() +# One transport (= one connection pool) per proxy; None is the direct pool. +# pyqwest transports are thread-safe and loop-independent, so the caches are +# process-global rather than per-thread (sync) or per-event-loop (async). +_sync_transports: Dict[Optional[ProxyConfig], PyqwestTransport] = {} +_async_transports: Dict[Optional[ProxyConfig], AsyncPyqwestTransport] = {} + + +def _transport_kwargs(proxy: Optional[ProxyConfig]) -> dict: + return dict( + # System CA certs, without which TLS through an intercepting proxy + # fails. + tls_include_system_certs=True, + proxy=proxy.to_pyqwest() if proxy is not None else None, + http_version=HTTPVersion.HTTP1, + pool_idle_timeout=pool_idle_timeout, + pool_max_idle_per_host=pool_max_idle_per_host, + # Redirects belong to the httpx client above, not to reqwest. + follow_redirects=False, + ) + + +def get_sync_transport(config: ConnectionConfig) -> PyqwestTransport: + """The shared HTTP/1.1 transport for synchronous Jupyter requests.""" + proxy = proxy_to_config(config.proxy) + with _transport_lock: + transport = _sync_transports.get(proxy) + if transport is None: + transport = PyqwestTransport( + SyncConnectionRetryTransport( + SyncHTTPTransport(**_transport_kwargs(proxy)), + max_retries=connection_retries, + ) + ) + _sync_transports[proxy] = transport + return transport + + +def get_async_transport(config: ConnectionConfig) -> AsyncPyqwestTransport: + """The shared HTTP/1.1 transport for asynchronous Jupyter requests.""" + proxy = proxy_to_config(config.proxy) + with _transport_lock: + transport = _async_transports.get(proxy) + if transport is None: + transport = AsyncPyqwestTransport( + AsyncConnectionRetryTransport( + HTTPTransport(**_transport_kwargs(proxy)), + max_retries=connection_retries, + ) + ) + _async_transports[proxy] = transport + return transport From c023b3cce65da6c243fad11ef3afc55a4081e3a9 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:22:14 +0200 Subject: [PATCH 3/5] Revert "fix(python): build own HTTP/1.1 transport for Jupyter requests" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d268be9. Duplicating the SDK's transport internals here only deepened the private-API coupling that broke in the first place, so the HTTP-version option is going upstream instead — see .context/upstream-http1-transport-proposal.md. The `get_transport(config, http2=False)` call sites are the end state already and need no change once e2b restores the parameter; the Python floor in pyproject.toml then moves to that release. Until it ships, the Python half of this bump cannot pass CI. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/shaggy-plums-wave.md | 4 +- .../code_interpreter_async.py | 22 +++-- .../code_interpreter_sync.py | 21 +++-- python/e2b_code_interpreter/transport.py | 90 ------------------- 4 files changed, 34 insertions(+), 103 deletions(-) delete mode 100644 python/e2b_code_interpreter/transport.py diff --git a/.changeset/shaggy-plums-wave.md b/.changeset/shaggy-plums-wave.md index 0fee642c..7b6084c2 100644 --- a/.changeset/shaggy-plums-wave.md +++ b/.changeset/shaggy-plums-wave.md @@ -3,6 +3,4 @@ '@e2b/code-interpreter-python': patch --- -Bump the E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0. - -The Python SDK moved its HTTP stack onto [`pyqwest`](https://pypi.org/project/pyqwest/), and its internal `get_transport()` helper no longer takes an `http2` argument — the HTTP version is negotiated by ALPN instead, which means HTTP/2 against the sandbox. Jupyter requests build their own HTTP/1.1 transport now (`e2b_code_interpreter.transport`), from the same pool tuning and connect-only retry policy the SDK uses, so client disconnects keep propagating to the server as a TCP close and long-running executions stay reliably cancellable. +Bump E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0 diff --git a/python/e2b_code_interpreter/code_interpreter_async.py b/python/e2b_code_interpreter/code_interpreter_async.py index e5ad107d..82a58b51 100644 --- a/python/e2b_code_interpreter/code_interpreter_async.py +++ b/python/e2b_code_interpreter/code_interpreter_async.py @@ -8,7 +8,7 @@ AsyncSandbox as BaseAsyncSandbox, InvalidArgumentException, ) -from e2b_code_interpreter.transport import get_async_transport +from e2b.api.client_async import get_transport from e2b_code_interpreter.constants import ( DEFAULT_TEMPLATE, @@ -70,11 +70,23 @@ def _jupyter_url(self) -> str: @property def _client(self) -> AsyncClient: - # Use a dedicated HTTP/1.1 transport for Jupyter requests so that - # client disconnects propagate to the server; see the module docstring - # of `e2b_code_interpreter.transport`. + # TODO: Remove later + # Use a dedicated HTTP/1.1 transport for Jupyter requests. + # + # The base SDK's shared transport now defaults to http2=True. With + # HTTP/2, multiple requests are multiplexed over a single TCP + # connection, so when a client cancels a request (e.g. the caller + # disconnects from the streaming `/execute` endpoint) the server + # may not detect the disconnect: only the HTTP/2 stream is + # cancelled, the underlying TCP connection stays open. + # + # Forcing HTTP/1.1 here keeps the 1:1 mapping between TCP + # connection and request, so client disconnects propagate to the + # server as a TCP close and long-running executions can be + # cancelled reliably. The helper also caches the transport + # per-event-loop for async. return AsyncClient( - transport=get_async_transport(self.connection_config), + transport=get_transport(self.connection_config, http2=False), ) async def _handle_connection_error(self, err: Exception) -> None: diff --git a/python/e2b_code_interpreter/code_interpreter_sync.py b/python/e2b_code_interpreter/code_interpreter_sync.py index 800e006b..b5ce8f2b 100644 --- a/python/e2b_code_interpreter/code_interpreter_sync.py +++ b/python/e2b_code_interpreter/code_interpreter_sync.py @@ -4,7 +4,7 @@ from typing import Optional, Dict, overload, Union, List from httpx import Client from e2b import Sandbox as BaseSandbox, InvalidArgumentException -from e2b_code_interpreter.transport import get_sync_transport +from e2b.api.client_sync import get_transport from e2b_code_interpreter.constants import ( DEFAULT_TEMPLATE, @@ -67,10 +67,21 @@ def _jupyter_url(self) -> str: @property def _client(self) -> Client: - # Use a dedicated HTTP/1.1 transport for Jupyter requests so that - # client disconnects propagate to the server; see the module docstring - # of `e2b_code_interpreter.transport`. - return Client(transport=get_sync_transport(self.connection_config)) + # TODO: Remove later + # Use a dedicated HTTP/1.1 transport for Jupyter requests. + # + # The base SDK's shared transport now defaults to http2=True. With + # HTTP/2, multiple requests are multiplexed over a single TCP + # connection, so when a client cancels a request (e.g. the caller + # disconnects from the streaming `/execute` endpoint) the server + # may not detect the disconnect: only the HTTP/2 stream is + # cancelled, the underlying TCP connection stays open. + # + # Forcing HTTP/1.1 here keeps the 1:1 mapping between TCP + # connection and request, so client disconnects propagate to the + # server as a TCP close and long-running executions can be + # cancelled reliably. + return Client(transport=get_transport(self.connection_config, http2=False)) def _handle_connection_error(self, err: Exception) -> None: """ diff --git a/python/e2b_code_interpreter/transport.py b/python/e2b_code_interpreter/transport.py deleted file mode 100644 index 431132aa..00000000 --- a/python/e2b_code_interpreter/transport.py +++ /dev/null @@ -1,90 +0,0 @@ -"""HTTP/1.1 transports for Jupyter requests. - -# TODO: Remove later -The base SDK's shared transports let ALPN negotiate the HTTP version, which -means HTTP/2 against the sandbox. With HTTP/2, multiple requests are -multiplexed over a single TCP connection, so when a client cancels a request -(e.g. the caller disconnects from the streaming `/execute` endpoint) the -server may not detect the disconnect: only the HTTP/2 stream is cancelled, -the underlying TCP connection stays open. - -Forcing HTTP/1.1 keeps the 1:1 mapping between TCP connection and request, so -client disconnects propagate to the server as a TCP close, uvicorn delivers -`http.disconnect`, and long-running executions can be cancelled reliably. - -The base SDK has no option for this, so we build the transport ourselves from -the same pieces it uses (its pool tuning and connect-only retry policy) with -`http_version` pinned to HTTP/1.1. -""" - -import threading -from typing import Dict, Optional - -from pyqwest import HTTPTransport, HTTPVersion, SyncHTTPTransport -from pyqwest.httpx import AsyncPyqwestTransport, PyqwestTransport - -from e2b.api import ( - ProxyConfig, - connection_retries, - pool_idle_timeout, - pool_max_idle_per_host, - proxy_to_config, -) -from e2b.api.client_async import ( - ConnectionRetryTransport as AsyncConnectionRetryTransport, -) -from e2b.api.client_sync import ConnectionRetryTransport as SyncConnectionRetryTransport -from e2b.connection_config import ConnectionConfig - -_transport_lock = threading.Lock() -# One transport (= one connection pool) per proxy; None is the direct pool. -# pyqwest transports are thread-safe and loop-independent, so the caches are -# process-global rather than per-thread (sync) or per-event-loop (async). -_sync_transports: Dict[Optional[ProxyConfig], PyqwestTransport] = {} -_async_transports: Dict[Optional[ProxyConfig], AsyncPyqwestTransport] = {} - - -def _transport_kwargs(proxy: Optional[ProxyConfig]) -> dict: - return dict( - # System CA certs, without which TLS through an intercepting proxy - # fails. - tls_include_system_certs=True, - proxy=proxy.to_pyqwest() if proxy is not None else None, - http_version=HTTPVersion.HTTP1, - pool_idle_timeout=pool_idle_timeout, - pool_max_idle_per_host=pool_max_idle_per_host, - # Redirects belong to the httpx client above, not to reqwest. - follow_redirects=False, - ) - - -def get_sync_transport(config: ConnectionConfig) -> PyqwestTransport: - """The shared HTTP/1.1 transport for synchronous Jupyter requests.""" - proxy = proxy_to_config(config.proxy) - with _transport_lock: - transport = _sync_transports.get(proxy) - if transport is None: - transport = PyqwestTransport( - SyncConnectionRetryTransport( - SyncHTTPTransport(**_transport_kwargs(proxy)), - max_retries=connection_retries, - ) - ) - _sync_transports[proxy] = transport - return transport - - -def get_async_transport(config: ConnectionConfig) -> AsyncPyqwestTransport: - """The shared HTTP/1.1 transport for asynchronous Jupyter requests.""" - proxy = proxy_to_config(config.proxy) - with _transport_lock: - transport = _async_transports.get(proxy) - if transport is None: - transport = AsyncPyqwestTransport( - AsyncConnectionRetryTransport( - HTTPTransport(**_transport_kwargs(proxy)), - max_retries=connection_retries, - ) - ) - _async_transports[proxy] = transport - return transport From 3a72827f8595376273368f4ac5e1d61773030c1f Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:12:19 +0200 Subject: [PATCH 4/5] chore: bump e2b to 2.39.0 (js) and 2.39.1 (python) e2b 2.39.1 restores the `http2` parameter on `get_transport` / `get_envd_transport` that the pyqwest migration dropped in 2.38.0, so the HTTP/1.1 transport for Jupyter requests works again with no changes on our side. The Python floor is 2.39.1 rather than 2.39.0 because anything in `>=2.38.0, <2.39.1` raises TypeError on every code execution. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/shaggy-plums-wave.md | 4 +++- js/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- python/poetry.lock | 8 ++++---- python/pyproject.toml | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.changeset/shaggy-plums-wave.md b/.changeset/shaggy-plums-wave.md index 7b6084c2..a782e65a 100644 --- a/.changeset/shaggy-plums-wave.md +++ b/.changeset/shaggy-plums-wave.md @@ -3,4 +3,6 @@ '@e2b/code-interpreter-python': patch --- -Bump E2B SDK dependency: JavaScript to 2.38.3, Python to 2.38.0 +Bump E2B SDK dependency: JavaScript to 2.39.0, Python to 2.39.1. + +The Python floor is 2.39.1 specifically: e2b 2.38.0 moved the SDK's HTTP stack onto [`pyqwest`](https://pypi.org/project/pyqwest/) and dropped the `http2` parameter that Jupyter requests rely on to pin HTTP/1.1, so any e2b in `>=2.38.0, <2.39.1` raises `TypeError` on every code execution. 2.39.1 restores it. diff --git a/js/package.json b/js/package.json index 6493fe9e..bd9602c8 100644 --- a/js/package.json +++ b/js/package.json @@ -71,6 +71,6 @@ "defaults" ], "dependencies": { - "e2b": "^2.38.3" + "e2b": "^2.39.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b479787..d9ebd516 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: js: dependencies: e2b: - specifier: ^2.38.3 - version: 2.38.3 + specifier: ^2.39.0 + version: 2.39.0 devDependencies: '@types/node': specifier: ^20.19.19 @@ -1083,8 +1083,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - e2b@2.38.3: - resolution: {integrity: sha512-0tOokTvlltpYGO4UAMNcoSSkFEn2VHAGaJtZsMVhPf5GMmdE9RNcfla/KwCpaE8ef7cFKpSXCs+UUDyE/r/fMQ==} + e2b@2.39.0: + resolution: {integrity: sha512-MUhTgRqznSTs6Py1fU9ji2bHPHptYY6MjopKOqBu6fAQHHjynPjD8nrKPzxtgVkxxFtVkVRt68UGFHGCJgb5bg==} engines: {node: '>=20.18.1 <21 || >=22'} es-module-lexer@2.3.0: @@ -2782,7 +2782,7 @@ snapshots: dotenv@16.6.1: {} - e2b@2.38.3: + e2b@2.39.0: dependencies: '@bufbuild/protobuf': 2.12.1 '@connectrpc/connect': 2.1.2(@bufbuild/protobuf@2.12.1) diff --git a/python/poetry.lock b/python/poetry.lock index 4a6548fc..276d988b 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -205,14 +205,14 @@ files = [ [[package]] name = "e2b" -version = "2.38.0" +version = "2.39.1" description = "E2B SDK that give agents cloud environments" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "e2b-2.38.0-py3-none-any.whl", hash = "sha256:9f43803aa23e9a33ed48c1c778d588f83c0390db21edf3098255137981541b67"}, - {file = "e2b-2.38.0.tar.gz", hash = "sha256:b245976cc946d144baf77341b19fa537bfd7cbd9028dc8ef59bd6947f5e329c1"}, + {file = "e2b-2.39.1-py3-none-any.whl", hash = "sha256:71595fc48ebb00c549cef28b0a93fefff83407d43d21d7daa63e940614696a95"}, + {file = "e2b-2.39.1.tar.gz", hash = "sha256:458ca8dc4c1ed39e66fed6dcefae02b4328b79c7f04b707622f259fcec392293"}, ] [package.dependencies] @@ -1317,4 +1317,4 @@ bracex = ">=2.1.1" [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "ce4d7b40fe317581c81fae1d8ca0279d144b21fe48bb67298f982e4956c3b060" +content-hash = "285cb30a42aacdf9939f8135ab9b94bd178089ad8a1a1cf375086dfd424446fb" diff --git a/python/pyproject.toml b/python/pyproject.toml index 919de9ed..01083b5e 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -14,7 +14,7 @@ python = "^3.10" httpx = ">=0.20.0, <1.0.0" attrs = ">=21.3.0" -e2b = "^2.38.0" +e2b = "^2.39.1" [tool.poetry.group.dev.dependencies] pytest = "^9.0.3" From 2d4b23208207ffacd0827896a93c6644a962a857 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:41:55 +0200 Subject: [PATCH 5/5] fix(python): make run_code's timeout bound the execution again The SDK's pyqwest-backed transport does not implement httpx's per-phase timeouts; it derives one whole-request deadline and takes the longest non-connect phase. Passing `(request_timeout, timeout, request_timeout, request_timeout)` therefore yielded a deadline of `max(timeout, request_timeout)`, silently discarding any `timeout` shorter than `request_timeout` (60s by default). That is what broke test_subsequent_execution_works_after_client_timeout: `run_code(timeout=3)` ran for ~57s instead of 3s, outliving the fixture's 60s sandbox, so the next call got a 502 "sandbox was not found" reported as a sandbox TimeoutException. Put `timeout` on every non-connect phase so the derived deadline is exactly `timeout`, and pass an all-None Timeout for `timeout=0` so a deliberately unbounded execution isn't capped by the connect timeout instead. `timeout` is now a hard deadline on the request rather than an idle read bound, which matches the documented meaning and the JS SDK's body-abort timer. Add tests/test_execute_timeout.py, which runs offline and fails on a bare tuple or a request_timeout left on the write or pool phase. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/shaggy-plums-wave.md | 2 + .../code_interpreter_async.py | 17 ++- .../code_interpreter_sync.py | 17 ++- python/tests/test_execute_timeout.py | 123 ++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 python/tests/test_execute_timeout.py diff --git a/.changeset/shaggy-plums-wave.md b/.changeset/shaggy-plums-wave.md index a782e65a..b43ec134 100644 --- a/.changeset/shaggy-plums-wave.md +++ b/.changeset/shaggy-plums-wave.md @@ -6,3 +6,5 @@ Bump E2B SDK dependency: JavaScript to 2.39.0, Python to 2.39.1. The Python floor is 2.39.1 specifically: e2b 2.38.0 moved the SDK's HTTP stack onto [`pyqwest`](https://pypi.org/project/pyqwest/) and dropped the `http2` parameter that Jupyter requests rely on to pin HTTP/1.1, so any e2b in `>=2.38.0, <2.39.1` raises `TypeError` on every code execution. 2.39.1 restores it. + +Also fixes `run_code`'s `timeout` in the Python SDK. The new transport collapses httpx's per-phase timeouts into a single whole-request deadline and takes the longest phase, so passing `timeout` as the read timeout alongside `request_timeout` on the write and pool phases made the effective deadline `max(timeout, request_timeout)` — any `timeout` shorter than `request_timeout` (60s by default) was ignored and the execution ran on until `request_timeout`. `timeout` now bounds the request on its own, and `timeout=0` disables the deadline instead of inheriting the connect timeout. diff --git a/python/e2b_code_interpreter/code_interpreter_async.py b/python/e2b_code_interpreter/code_interpreter_async.py index 82a58b51..d34db76d 100644 --- a/python/e2b_code_interpreter/code_interpreter_async.py +++ b/python/e2b_code_interpreter/code_interpreter_async.py @@ -219,7 +219,22 @@ async def run_code( "env_vars": envs, }, headers=headers, - timeout=(request_timeout, timeout, request_timeout, request_timeout), + # `timeout` bounds the execution, `request_timeout` only the + # connect. Every non-connect phase must carry `timeout`: the + # SDK's pyqwest-backed transport collapses the per-phase + # timeouts into a single whole-request deadline and takes the + # longest of them, so leaving `request_timeout` on the write + # and pool phases would raise the floor to + # `max(timeout, request_timeout)` and silently ignore any + # `timeout` shorter than it. This matches the JS SDK, which + # aborts the execution on a `timeout`-long timer. + # `timeout=0` disables the deadline entirely; the transport + # still bounds connect on its own. + timeout=( + httpx.Timeout(timeout, connect=request_timeout) + if timeout is not None + else httpx.Timeout(None) + ), ) as response: err = await aextract_exception(response) if err: diff --git a/python/e2b_code_interpreter/code_interpreter_sync.py b/python/e2b_code_interpreter/code_interpreter_sync.py index b5ce8f2b..f4f3f8e6 100644 --- a/python/e2b_code_interpreter/code_interpreter_sync.py +++ b/python/e2b_code_interpreter/code_interpreter_sync.py @@ -214,7 +214,22 @@ def run_code( "env_vars": envs, }, headers=headers, - timeout=(request_timeout, timeout, request_timeout, request_timeout), + # `timeout` bounds the execution, `request_timeout` only the + # connect. Every non-connect phase must carry `timeout`: the + # SDK's pyqwest-backed transport collapses the per-phase + # timeouts into a single whole-request deadline and takes the + # longest of them, so leaving `request_timeout` on the write + # and pool phases would raise the floor to + # `max(timeout, request_timeout)` and silently ignore any + # `timeout` shorter than it. This matches the JS SDK, which + # aborts the execution on a `timeout`-long timer. + # `timeout=0` disables the deadline entirely; the transport + # still bounds connect on its own. + timeout=( + httpx.Timeout(timeout, connect=request_timeout) + if timeout is not None + else httpx.Timeout(None) + ), ) as response: err = extract_exception(response) if err: diff --git a/python/tests/test_execute_timeout.py b/python/tests/test_execute_timeout.py new file mode 100644 index 00000000..0ea523d9 --- /dev/null +++ b/python/tests/test_execute_timeout.py @@ -0,0 +1,123 @@ +"""The execution timeout must reach the transport as the request deadline. + +Regression test for the SDK's pyqwest-backed transport collapsing httpx's +per-phase timeouts into a single whole-request deadline equal to the longest +phase. With the previous `(request_timeout, timeout, request_timeout, +request_timeout)` tuple that deadline came out as `max(timeout, +request_timeout)`, so any `timeout` shorter than `request_timeout` was silently +ignored and an execution ran on until `request_timeout` instead. +""" + +import httpx +import pytest + +from e2b.connection_config import ConnectionConfig + +from e2b_code_interpreter.code_interpreter_async import AsyncSandbox +from e2b_code_interpreter.code_interpreter_sync import Sandbox + +REQUEST_TIMEOUT = 60.0 + + +class _Stop(Exception): + """Unwinds `run_code` once the request timeout has been captured. + + Not an `httpx` error, so `run_code`'s own except clauses let it through. + """ + + +class _CapturingClient: + def __init__(self, captured: dict): + self._captured = captured + + def stream(self, *args, **kwargs): + self._captured.update(kwargs) + raise _Stop + + +def _sandbox(cls, captured: dict): + class _Fake(cls): + @property + def connection_config(self): + return ConnectionConfig( + api_key="x", domain="e2b.app", request_timeout=REQUEST_TIMEOUT + ) + + @property + def sandbox_id(self): + return "sandbox-id" + + @property + def _envd_access_token(self): + return None + + @property + def traffic_access_token(self): + return None + + @property + def _jupyter_url(self): + return "http://127.0.0.1:9" + + @property + def _client(self): + return _CapturingClient(captured) + + return _Fake.__new__(_Fake) + + +def _captured_timeout(captured: dict) -> httpx.Timeout: + timeout = captured["timeout"] + assert isinstance(timeout, httpx.Timeout), ( + "a plain tuple lets the transport pick the longest phase as the deadline; " + f"got {timeout!r}" + ) + return timeout + + +@pytest.mark.parametrize("timeout", [3, 10, 300]) +def test_execution_timeout_is_the_request_deadline(timeout): + captured: dict = {} + with pytest.raises(_Stop): + _sandbox(Sandbox, captured).run_code("1 + 1", timeout=timeout) + + tmo = _captured_timeout(captured) + # Every non-connect phase carries `timeout`, so the deadline the transport + # derives is `timeout` and not `max(timeout, request_timeout)`. + assert tmo.read == timeout + assert tmo.write == timeout + assert tmo.pool == timeout + assert tmo.connect == REQUEST_TIMEOUT + + +@pytest.mark.parametrize("timeout", [3, 10, 300]) +async def test_async_execution_timeout_is_the_request_deadline(timeout): + captured: dict = {} + with pytest.raises(_Stop): + await _sandbox(AsyncSandbox, captured).run_code("1 + 1", timeout=timeout) + + tmo = _captured_timeout(captured) + assert tmo.read == timeout + assert tmo.write == timeout + assert tmo.pool == timeout + assert tmo.connect == REQUEST_TIMEOUT + + +def test_zero_timeout_disables_the_deadline(): + captured: dict = {} + with pytest.raises(_Stop): + _sandbox(Sandbox, captured).run_code("1 + 1", timeout=0) + + tmo = _captured_timeout(captured) + # `connect` has to go too: with the other phases unset the transport falls + # back to it, which would cap a deliberately unbounded execution. + assert (tmo.read, tmo.write, tmo.pool, tmo.connect) == (None, None, None, None) + + +async def test_async_zero_timeout_disables_the_deadline(): + captured: dict = {} + with pytest.raises(_Stop): + await _sandbox(AsyncSandbox, captured).run_code("1 + 1", timeout=0) + + tmo = _captured_timeout(captured) + assert (tmo.read, tmo.write, tmo.pool, tmo.connect) == (None, None, None, None)