From def2bd720a233fddb825a15b41059791a7928767 Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Fri, 11 Sep 2026 11:25:15 +0200 Subject: [PATCH 1/6] fix(sdk): apply the upload headers the API returns with a file upload link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Azure Blob Storage requires the request header x-ms-blob-type on Put Blob, which a signed URL cannot carry, so every template build with a COPY instruction failed on Azure-backed clusters. The API now returns the headers alongside the upload URL; both SDKs send them verbatim on the PUT and keep their own Content-Length. GCS- and S3-backed clusters return no headers, so their presigned PUTs go out with the same header set as before — their signatures cover the header list. Co-Authored-By: Claude Opus 5 --- .../azure-template-upload-headers-python.md | 5 ++ .changeset/azure-template-upload-headers.md | 5 ++ packages/js-sdk/src/api/schema.gen.ts | 8 +++ packages/js-sdk/src/template/buildApi.ts | 10 +++- packages/js-sdk/src/template/index.ts | 3 +- .../js-sdk/tests/template/uploadFile.test.ts | 40 +++++++++++++ .../client/api/templates/post_templates.py | 4 ++ .../templates/post_templates_template_id.py | 4 ++ .../client/api/templates/post_v2_templates.py | 4 ++ .../client/api/templates/post_v3_templates.py | 4 ++ .../e2b/api/client/models/__init__.py | 2 + .../models/template_build_file_upload.py | 29 +++++++++- .../template_build_file_upload_headers.py | 44 ++++++++++++++ .../e2b/template_async/build_api.py | 8 ++- .../python-sdk/e2b/template_async/main.py | 1 + .../python-sdk/e2b/template_sync/build_api.py | 7 ++- packages/python-sdk/e2b/template_sync/main.py | 1 + .../async/template_async/test_upload_file.py | 58 +++++++++++++++++++ .../sync/template_sync/test_upload_file.py | 58 +++++++++++++++++++ spec/openapi.yml | 31 ++++++++++ spec/runtime-ref | 2 +- 21 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 .changeset/azure-template-upload-headers-python.md create mode 100644 .changeset/azure-template-upload-headers.md create mode 100644 packages/python-sdk/e2b/api/client/models/template_build_file_upload_headers.py diff --git a/.changeset/azure-template-upload-headers-python.md b/.changeset/azure-template-upload-headers-python.md new file mode 100644 index 0000000000..fbbefca86e --- /dev/null +++ b/.changeset/azure-template-upload-headers-python.md @@ -0,0 +1,5 @@ +--- +"@e2b/python-sdk": patch +--- + +Apply the request headers the API returns with a template layer-file upload link. Azure Blob Storage requires `x-ms-blob-type` on the upload request, which its signed URL cannot carry, so `COPY` instructions failed on Azure-backed clusters. GCS- and S3-backed clusters return no headers and are unaffected. diff --git a/.changeset/azure-template-upload-headers.md b/.changeset/azure-template-upload-headers.md new file mode 100644 index 0000000000..ddf3f9417a --- /dev/null +++ b/.changeset/azure-template-upload-headers.md @@ -0,0 +1,5 @@ +--- +"e2b": patch +--- + +Apply the request headers the API returns with a template layer-file upload link. Azure Blob Storage requires `x-ms-blob-type` on the upload request, which its signed URL cannot carry, so `COPY` instructions failed on Azure-backed clusters. GCS- and S3-backed clusters return no headers and are unaffected. diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 8943b3385e..fa424a8a4a 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -1162,6 +1162,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 409: components["responses"]["409"]; 500: components["responses"]["500"]; }; }; @@ -1243,6 +1244,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 409: components["responses"]["409"]; 500: components["responses"]["500"]; }; }; @@ -1854,6 +1856,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 409: components["responses"]["409"]; 500: components["responses"]["500"]; }; }; @@ -1995,6 +1998,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 403: components["responses"]["403"]; + 409: components["responses"]["409"]; 500: components["responses"]["500"]; }; }; @@ -2856,6 +2860,10 @@ export interface components { updatedAt: string; }; TemplateBuildFileUpload: { + /** @description Request headers that must be sent with the upload request */ + headers?: { + [key: string]: string; + }; /** @description Whether the file is already present in the cache */ present: boolean; /** @description Url where the file should be uploaded to */ diff --git a/packages/js-sdk/src/template/buildApi.ts b/packages/js-sdk/src/template/buildApi.ts index 489445c4ee..ef1860a322 100644 --- a/packages/js-sdk/src/template/buildApi.ts +++ b/packages/js-sdk/src/template/buildApi.ts @@ -113,6 +113,7 @@ export async function uploadFile( fileName: string fileContextPath: string url: string + headers?: Record ignorePatterns: string[] resolveSymlinks: boolean gzip: boolean @@ -128,6 +129,7 @@ export async function uploadFile( const { fileName, url, + headers, fileContextPath, ignorePatterns, resolveSymlinks, @@ -154,7 +156,7 @@ export async function uploadFile( abortOpts?.signal ) - const res = await putFileStream(url, tar.path, tar.size, signal) + const res = await putFileStream(url, tar.path, tar.size, signal, headers) if (!res.ok) { throw new FileUploadError( @@ -176,7 +178,8 @@ async function putFileStream( url: string, filePath: string, size: number, - signal: AbortSignal | undefined + signal: AbortSignal | undefined, + headers?: Record ): Promise<{ ok: boolean; statusText: string }> { // Prefer undici's fetch: it honors the explicit Content-Length on stream // bodies on every runtime, while Deno's native fetch ignores the header and @@ -192,7 +195,10 @@ async function putFileStream( body: stream.Readable.toWeb( fs.createReadStream(filePath) ) as ReadableStream, + // Headers the API asked for, applied as given (Azure's Put Blob requires + // x-ms-blob-type, which its SAS cannot carry). Content-Length stays ours. headers: { + ...headers, 'Content-Length': size.toString(), }, // Streaming request bodies require half-duplex mode. diff --git a/packages/js-sdk/src/template/index.ts b/packages/js-sdk/src/template/index.ts index 1cc6401b28..12ba6caeb6 100644 --- a/packages/js-sdk/src/template/index.ts +++ b/packages/js-sdk/src/template/index.ts @@ -1112,7 +1112,7 @@ export class TemplateBase stackTrace = this.stackTraces[index + 1] } - const { present, url } = await getFileUploadLink( + const { present, url, headers } = await getFileUploadLink( client, { templateID, @@ -1131,6 +1131,7 @@ export class TemplateBase fileName: src, fileContextPath: this.fileContextPath.toString(), url, + headers, ignorePatterns: [ ...this.fileIgnorePatterns, ...readDockerignore(this.fileContextPath.toString()), diff --git a/packages/js-sdk/tests/template/uploadFile.test.ts b/packages/js-sdk/tests/template/uploadFile.test.ts index e9f7492c9c..b953297e77 100644 --- a/packages/js-sdk/tests/template/uploadFile.test.ts +++ b/packages/js-sdk/tests/template/uploadFile.test.ts @@ -73,5 +73,45 @@ describe('uploadFile transfer encoding', () => { // Content-Type (e.g. inferred from the archive's file extension) makes // the storage backend reject the upload with 403 Forbidden. expect(capturedHeaders['content-type']).toBeUndefined() + + // S3 and GCS presigned PUTs sign the header set, so the upload must add + // nothing the API did not ask for. + expect(capturedHeaders['x-ms-blob-type']).toBeUndefined() + }) + + test('sends the headers the API returned with the upload link', async () => { + await uploadFile( + { + fileName: '*.txt', + fileContextPath: testDir, + url: baseUrl, + headers: { 'x-ms-blob-type': 'BlockBlob' }, + ignorePatterns: [], + resolveSymlinks: false, + gzip: true, + }, + undefined + ) + + // Azure's Put Blob rejects the request without it, and its SAS cannot + // carry a required request header, so the API hands it back instead. + expect(capturedHeaders['x-ms-blob-type']).toBe('BlockBlob') + }) + + test('keeps its own Content-Length when the API returns one', async () => { + await uploadFile( + { + fileName: '*.txt', + fileContextPath: testDir, + url: baseUrl, + headers: { 'Content-Length': '1' }, + ignorePatterns: [], + resolveSymlinks: false, + gzip: true, + }, + undefined + ) + + expect(Number(capturedHeaders['content-length'])).toBe(capturedBodyLength) }) }) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates.py index e561b2adb2..61f4f7b73e 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_templates.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + return response_409 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py index 067d1ebc6f..35e68d3ddb 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py @@ -42,6 +42,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + return response_409 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py index 0c3f568d10..0ed4b6d706 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + return response_409 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py index fd390477d7..3ae4afbd29 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py @@ -49,6 +49,10 @@ def _parse_response( response_403 = Error.from_dict(response.json()) return response_403 + if response.status_code == 409: + response_409 = Error.from_dict(response.json()) + + return response_409 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index d2acfa930d..8390964fc1 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -63,6 +63,7 @@ from .template_alias_response import TemplateAliasResponse from .template_build import TemplateBuild from .template_build_file_upload import TemplateBuildFileUpload +from .template_build_file_upload_headers import TemplateBuildFileUploadHeaders from .template_build_info import TemplateBuildInfo from .template_build_logs_response import TemplateBuildLogsResponse from .template_build_request import TemplateBuildRequest @@ -144,6 +145,7 @@ "TemplateAliasResponse", "TemplateBuild", "TemplateBuildFileUpload", + "TemplateBuildFileUploadHeaders", "TemplateBuildInfo", "TemplateBuildLogsResponse", "TemplateBuildRequest", diff --git a/packages/python-sdk/e2b/api/client/models/template_build_file_upload.py b/packages/python-sdk/e2b/api/client/models/template_build_file_upload.py index a7d4e44a04..aacdf46be7 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build_file_upload.py +++ b/packages/python-sdk/e2b/api/client/models/template_build_file_upload.py @@ -1,11 +1,17 @@ from collections.abc import Mapping -from typing import Any, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar, Union from attrs import define as _attrs_define from attrs import field as _attrs_field from ..types import UNSET, Unset +if TYPE_CHECKING: + from ..models.template_build_file_upload_headers import ( + TemplateBuildFileUploadHeaders, + ) + + T = TypeVar("T", bound="TemplateBuildFileUpload") @@ -15,10 +21,13 @@ class TemplateBuildFileUpload: Attributes: present (bool): Whether the file is already present in the cache url (Union[Unset, str]): Url where the file should be uploaded to + headers (Union[Unset, TemplateBuildFileUploadHeaders]): Request headers that must be sent with the upload + request """ present: bool url: Union[Unset, str] = UNSET + headers: Union[Unset, "TemplateBuildFileUploadHeaders"] = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -26,6 +35,10 @@ def to_dict(self) -> dict[str, Any]: url = self.url + headers: Union[Unset, dict[str, Any]] = UNSET + if not isinstance(self.headers, Unset): + headers = self.headers.to_dict() + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -35,19 +48,33 @@ def to_dict(self) -> dict[str, Any]: ) if url is not UNSET: field_dict["url"] = url + if headers is not UNSET: + field_dict["headers"] = headers return field_dict @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.template_build_file_upload_headers import ( + TemplateBuildFileUploadHeaders, + ) + d = dict(src_dict) present = d.pop("present") url = d.pop("url", UNSET) + _headers = d.pop("headers", UNSET) + headers: Union[Unset, TemplateBuildFileUploadHeaders] + if isinstance(_headers, Unset): + headers = UNSET + else: + headers = TemplateBuildFileUploadHeaders.from_dict(_headers) + template_build_file_upload = cls( present=present, url=url, + headers=headers, ) template_build_file_upload.additional_properties = d diff --git a/packages/python-sdk/e2b/api/client/models/template_build_file_upload_headers.py b/packages/python-sdk/e2b/api/client/models/template_build_file_upload_headers.py new file mode 100644 index 0000000000..93bcb32717 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/template_build_file_upload_headers.py @@ -0,0 +1,44 @@ +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="TemplateBuildFileUploadHeaders") + + +@_attrs_define +class TemplateBuildFileUploadHeaders: + """Request headers that must be sent with the upload request""" + + additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + template_build_file_upload_headers = cls() + + template_build_file_upload_headers.additional_properties = d + return template_build_file_upload_headers + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> str: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: str) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/template_async/build_api.py b/packages/python-sdk/e2b/template_async/build_api.py index 328a002f9a..a8a324321d 100644 --- a/packages/python-sdk/e2b/template_async/build_api.py +++ b/packages/python-sdk/e2b/template_async/build_api.py @@ -1,7 +1,7 @@ import asyncio import os from types import TracebackType -from typing import Callable, Optional, List, Union +from typing import Callable, Dict, Optional, List, Union import httpx from pyqwest import HTTPTransport @@ -115,6 +115,7 @@ async def upload_file( resolve_symlinks: bool, gzip: bool, stack_trace: Optional[TracebackType], + headers: Optional[Dict[str, str]] = None, request_timeout: Optional[float] = None, ): # Uploading a large build-context archive can take far longer than the 60s @@ -156,10 +157,13 @@ async def upload_file( # explicit Content-Length suppresses chunked transfer # encoding, which S3 presigned URLs reject; reqwest keeps the # Content-Length framing for the streamed body. + # Headers the API asked for, applied as given (Azure's Put + # Blob requires x-ms-blob-type, which its SAS cannot carry). + # Content-Length stays ours. response = await client.put( url, content=aiter_io_chunks(tar_file), - headers={"Content-Length": str(size)}, + headers={**(headers or {}), "Content-Length": str(size)}, ) response.raise_for_status() finally: diff --git a/packages/python-sdk/e2b/template_async/main.py b/packages/python-sdk/e2b/template_async/main.py index 2247f8b0ae..0a9c0e0374 100644 --- a/packages/python-sdk/e2b/template_async/main.py +++ b/packages/python-sdk/e2b/template_async/main.py @@ -137,6 +137,7 @@ async def _build( resolve_symlinks, gzip, stack_trace, + headers=file_info.headers.to_dict() if file_info.headers else None, request_timeout=request_timeout, ) if on_build_logs: diff --git a/packages/python-sdk/e2b/template_sync/build_api.py b/packages/python-sdk/e2b/template_sync/build_api.py index 735dcf7970..0b956ff725 100644 --- a/packages/python-sdk/e2b/template_sync/build_api.py +++ b/packages/python-sdk/e2b/template_sync/build_api.py @@ -1,6 +1,6 @@ import time from types import TracebackType -from typing import Callable, Optional, List, Union +from typing import Callable, Dict, Optional, List, Union import httpx from pyqwest import SyncHTTPTransport @@ -113,6 +113,7 @@ def upload_file( resolve_symlinks: bool, gzip: bool, stack_trace: Optional[TracebackType], + headers: Optional[Dict[str, str]] = None, request_timeout: Optional[float] = None, ): # Uploading a large build-context archive can take far longer than the 60s @@ -152,7 +153,9 @@ def upload_file( # Content-Length from the file size—S3 presigned URLs reject # chunked transfer encoding, and reqwest keeps the # Content-Length framing for the streamed body. - response = client.put(url, content=tar_file) + # Headers the API asked for, applied as given (Azure's Put + # Blob requires x-ms-blob-type, which its SAS cannot carry). + response = client.put(url, content=tar_file, headers=headers) response.raise_for_status() finally: # Closing the spooled temp file is best-effort: a failure here diff --git a/packages/python-sdk/e2b/template_sync/main.py b/packages/python-sdk/e2b/template_sync/main.py index 1481966e04..55598cd749 100644 --- a/packages/python-sdk/e2b/template_sync/main.py +++ b/packages/python-sdk/e2b/template_sync/main.py @@ -137,6 +137,7 @@ def _build( resolve_symlinks, gzip, stack_trace, + headers=file_info.headers.to_dict() if file_info.headers else None, request_timeout=request_timeout, ) if on_build_logs: diff --git a/packages/python-sdk/tests/async/template_async/test_upload_file.py b/packages/python-sdk/tests/async/template_async/test_upload_file.py index a8d2993427..2274fc7fb9 100644 --- a/packages/python-sdk/tests/async/template_async/test_upload_file.py +++ b/packages/python-sdk/tests/async/template_async/test_upload_file.py @@ -240,3 +240,61 @@ def failing_close_stream(*args, **kwargs): thread.join(timeout=5) assert state["headers"] is not None + + +async def test_upload_file_sends_the_headers_the_api_returned(tmp_path): + # Azure's Put Blob rejects the request without x-ms-blob-type, and its SAS + # cannot carry a required request header, so the API hands it back with the + # upload link for the client to apply. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + await upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + headers={"x-ms-blob-type": "BlockBlob"}, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert state["headers"]["x-ms-blob-type"] == "BlockBlob" + + +async def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): + # S3 and GCS presigned PUTs sign the header set, so the upload must add + # nothing the API did not ask for. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + await upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert "x-ms-blob-type" not in state["headers"] diff --git a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py index a08ae9906a..617ce8a304 100644 --- a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py +++ b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py @@ -236,3 +236,61 @@ def failing_close_stream(*args, **kwargs): thread.join(timeout=5) assert state["headers"] is not None + + +def test_upload_file_sends_the_headers_the_api_returned(tmp_path): + # Azure's Put Blob rejects the request without x-ms-blob-type, and its SAS + # cannot carry a required request header, so the API hands it back with the + # upload link for the client to apply. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + headers={"x-ms-blob-type": "BlockBlob"}, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert state["headers"]["x-ms-blob-type"] == "BlockBlob" + + +def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): + # S3 and GCS presigned PUTs sign the header set, so the upload must add + # nothing the API did not ask for. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert "x-ms-blob-type" not in state["headers"] diff --git a/spec/openapi.yml b/spec/openapi.yml index e7787b451f..6df979f231 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -1651,6 +1651,11 @@ components: url: description: Url where the file should be uploaded to type: string + headers: + description: Request headers that must be sent with the upload request + type: object + additionalProperties: + type: string LogLevel: type: string @@ -1948,6 +1953,15 @@ components: type: integer format: uint32 description: Number of sandboxes running on the node + maxSandboxes: + type: integer + format: int64 + description: Node-scoped configured sandbox admission limit. Nonpositive values reject creation. Omitted when unknown or not an orchestrator. + outstandingWork: + type: integer + format: uint64 + minimum: 0 + description: Observed work holds on the node. Omitted when unknown; zero does not authorize deletion. metrics: $ref: "#/components/schemas/NodeMetrics" createSuccesses: @@ -2005,6 +2019,15 @@ components: type: integer format: uint32 description: Number of sandboxes running on the node + maxSandboxes: + type: integer + format: int64 + description: Node-scoped configured sandbox admission limit. Nonpositive values reject creation. Omitted when unknown or not an orchestrator. + outstandingWork: + type: integer + format: uint64 + minimum: 0 + description: Observed work holds on the node. Omitted when unknown; zero does not authorize deletion. metrics: $ref: "#/components/schemas/NodeMetrics" createSuccesses: @@ -3233,6 +3256,8 @@ paths: $ref: "#/components/responses/401" "403": $ref: "#/components/responses/403" + "409": + $ref: "#/components/responses/409" "500": $ref: "#/components/responses/500" @@ -3309,6 +3334,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "409": + $ref: "#/components/responses/409" "500": $ref: "#/components/responses/500" @@ -3410,6 +3437,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "409": + $ref: "#/components/responses/409" "500": $ref: "#/components/responses/500" @@ -3470,6 +3499,8 @@ paths: $ref: "#/components/schemas/TemplateLegacy" "401": $ref: "#/components/responses/401" + "409": + $ref: "#/components/responses/409" "500": $ref: "#/components/responses/500" delete: diff --git a/spec/runtime-ref b/spec/runtime-ref index ce24be88db..3809a41f0c 100644 --- a/spec/runtime-ref +++ b/spec/runtime-ref @@ -1 +1 @@ -debf6bec7a59db73e3407d93ef94da5c82429089 +8cd25be70e2d9e6f9bb457c15fdad51b55bd4bdf From 105242e823c1a6b7df4b0857031c907ddf1c5512 Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Mon, 14 Sep 2026 10:08:18 +0200 Subject: [PATCH 2/6] fix(python-sdk): keep our Content-Length in the sync upload and make headers keyword-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review fixes: the sync upload_file now mirrors async — API-returned headers merge under our own Content-Length (forcing test added in both variants); headers is keyword-only in both signatures; the Unset check in main.py is the explicit isinstance spelling. Comments compressed to one line each. Co-Authored-By: Claude Fable 5 --- packages/js-sdk/src/api/schema.gen.ts | 216 +----------------- packages/js-sdk/src/template/buildApi.ts | 3 +- .../js-sdk/tests/template/uploadFile.test.ts | 6 +- .../e2b/template_async/build_api.py | 9 +- .../python-sdk/e2b/template_async/main.py | 7 +- .../python-sdk/e2b/template_sync/build_api.py | 16 +- packages/python-sdk/e2b/template_sync/main.py | 7 +- .../async/template_async/test_upload_file.py | 36 ++- .../sync/template_sync/test_upload_file.py | 36 ++- 9 files changed, 95 insertions(+), 241 deletions(-) diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index fa424a8a4a..1f498dbd6c 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -1133,39 +1133,7 @@ export interface paths { }; }; put?: never; - /** - * Create template - * @deprecated - * @description Create a new template - */ - post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TemplateBuildRequest"]; - }; - }; - responses: { - /** @description The build was accepted */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["TemplateLegacy"]; - }; - }; - 400: components["responses"]["400"]; - 401: components["responses"]["401"]; - 409: components["responses"]["409"]; - 500: components["responses"]["500"]; - }; - }; + post?: never; delete?: never; options?: never; head?: never; @@ -1214,40 +1182,7 @@ export interface paths { }; }; put?: never; - /** - * Rebuild template - * @deprecated - * @description Rebuild an template - */ - post: { - parameters: { - query?: never; - header?: never; - path: { - templateID: components["parameters"]["templateID"]; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TemplateBuildRequest"]; - }; - }; - responses: { - /** @description The build was accepted */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["TemplateLegacy"]; - }; - }; - 401: components["responses"]["401"]; - 409: components["responses"]["409"]; - 500: components["responses"]["500"]; - }; - }; + post?: never; /** * Delete template * @description Delete a template @@ -1310,49 +1245,6 @@ export interface paths { }; trace?: never; }; - "/templates/{templateID}/builds/{buildID}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Start template build - * @deprecated - * @description Start the build - */ - post: { - parameters: { - query?: never; - header?: never; - path: { - buildID: components["parameters"]["buildID"]; - templateID: components["parameters"]["templateID"]; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description The build has started */ - 202: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - 401: components["responses"]["401"]; - 500: components["responses"]["500"]; - }; - }; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/templates/{templateID}/builds/{buildID}/logs": { parameters: { query?: never; @@ -1827,39 +1719,7 @@ export interface paths { }; }; put?: never; - /** - * Create template (v2) - * @deprecated - * @description Create a new template - */ - post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TemplateBuildRequestV2"]; - }; - }; - responses: { - /** @description The build was requested successfully */ - 202: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["TemplateLegacy"]; - }; - }; - 400: components["responses"]["400"]; - 401: components["responses"]["401"]; - 409: components["responses"]["409"]; - 500: components["responses"]["500"]; - }; - }; + post?: never; delete?: never; options?: never; head?: never; @@ -1950,6 +1810,7 @@ export interface paths { }; content?: never; }; + 400: components["responses"]["400"]; 401: components["responses"]["401"]; 500: components["responses"]["500"]; }; @@ -2878,7 +2739,8 @@ export interface components { */ logEntries: components["schemas"]["BuildLogEntry"][]; /** - * @description Build logs + * @deprecated + * @description Build logs (always empty since the V1 build path was removed, use logEntries) * @default [] */ logs: string[]; @@ -2894,31 +2756,6 @@ export interface components { */ logs: components["schemas"]["BuildLogEntry"][]; }; - TemplateBuildRequest: { - /** @description Alias of the template */ - alias?: string; - cpuCount?: components["schemas"]["CPUCount"]; - /** @description Dockerfile for the template */ - dockerfile: string; - memoryMB?: components["schemas"]["MemoryMB"]; - /** @description Ready check command to execute in the template after the build */ - readyCmd?: string; - /** @description Start command to execute in the template after the build */ - startCmd?: string; - /** @description Identifier of the team */ - teamID?: string; - }; - TemplateBuildRequestV2: { - /** @description Alias of the template */ - alias: string; - cpuCount?: components["schemas"]["CPUCount"]; - memoryMB?: components["schemas"]["MemoryMB"]; - /** - * @deprecated - * @description Identifier of the team - */ - teamID?: string; - }; TemplateBuildRequestV3: { /** * @deprecated @@ -2938,6 +2775,7 @@ export interface components { */ teamID?: string; }; + /** @description Exactly one of fromImage or fromTemplate must be given and non-empty. */ TemplateBuildStartV2: { /** * @description Whether the whole build should be forced to run regardless of the cache @@ -2964,46 +2802,6 @@ export interface components { * @enum {string} */ TemplateBuildStatus: "building" | "waiting" | "ready" | "error"; - TemplateLegacy: { - /** @description Aliases of the template */ - aliases: string[]; - /** - * Format: int32 - * @description Number of times the template was built - */ - buildCount: number; - /** @description Identifier of the last successful build for given template */ - buildID: string; - cpuCount: components["schemas"]["CPUCount"]; - /** - * Format: date-time - * @description Time when the template was created - */ - createdAt: string; - createdBy: components["schemas"]["TeamUser"] | null; - diskSizeMB: components["schemas"]["DiskSizeMB"]; - envdVersion: components["schemas"]["EnvdVersion"]; - /** - * Format: date-time - * @description Time when the template was last used - */ - lastSpawnedAt: string | null; - memoryMB: components["schemas"]["MemoryMB"]; - /** @description Whether the template is public or only accessible by the team */ - public: boolean; - /** - * Format: int64 - * @description Number of times the template was used - */ - spawnCount: number; - /** @description Identifier of the template */ - templateID: string; - /** - * Format: date-time - * @description Time when the template was last updated - */ - updatedAt: string; - }; TemplateRequestResponseV3: { /** * @deprecated diff --git a/packages/js-sdk/src/template/buildApi.ts b/packages/js-sdk/src/template/buildApi.ts index ef1860a322..8c160b4e2d 100644 --- a/packages/js-sdk/src/template/buildApi.ts +++ b/packages/js-sdk/src/template/buildApi.ts @@ -195,8 +195,7 @@ async function putFileStream( body: stream.Readable.toWeb( fs.createReadStream(filePath) ) as ReadableStream, - // Headers the API asked for, applied as given (Azure's Put Blob requires - // x-ms-blob-type, which its SAS cannot carry). Content-Length stays ours. + // API-returned headers applied as given (Azure needs x-ms-blob-type, which a SAS cannot carry); Content-Length stays ours. headers: { ...headers, 'Content-Length': size.toString(), diff --git a/packages/js-sdk/tests/template/uploadFile.test.ts b/packages/js-sdk/tests/template/uploadFile.test.ts index b953297e77..2fb68fdc2e 100644 --- a/packages/js-sdk/tests/template/uploadFile.test.ts +++ b/packages/js-sdk/tests/template/uploadFile.test.ts @@ -74,8 +74,7 @@ describe('uploadFile transfer encoding', () => { // the storage backend reject the upload with 403 Forbidden. expect(capturedHeaders['content-type']).toBeUndefined() - // S3 and GCS presigned PUTs sign the header set, so the upload must add - // nothing the API did not ask for. + // S3/GCS presigned PUTs sign the header set — the upload must add nothing the API did not ask for. expect(capturedHeaders['x-ms-blob-type']).toBeUndefined() }) @@ -93,8 +92,7 @@ describe('uploadFile transfer encoding', () => { undefined ) - // Azure's Put Blob rejects the request without it, and its SAS cannot - // carry a required request header, so the API hands it back instead. + // Azure's Put Blob needs a request header a SAS cannot carry, so the API hands it back instead. expect(capturedHeaders['x-ms-blob-type']).toBe('BlockBlob') }) diff --git a/packages/python-sdk/e2b/template_async/build_api.py b/packages/python-sdk/e2b/template_async/build_api.py index a8a324321d..895e25e78d 100644 --- a/packages/python-sdk/e2b/template_async/build_api.py +++ b/packages/python-sdk/e2b/template_async/build_api.py @@ -115,6 +115,7 @@ async def upload_file( resolve_symlinks: bool, gzip: bool, stack_trace: Optional[TracebackType], + *, headers: Optional[Dict[str, str]] = None, request_timeout: Optional[float] = None, ): @@ -153,13 +154,7 @@ async def upload_file( ) ), ) as client: - # Stream the archive from disk via an async iterator. The - # explicit Content-Length suppresses chunked transfer - # encoding, which S3 presigned URLs reject; reqwest keeps the - # Content-Length framing for the streamed body. - # Headers the API asked for, applied as given (Azure's Put - # Blob requires x-ms-blob-type, which its SAS cannot carry). - # Content-Length stays ours. + # API-returned headers applied as given, but Content-Length stays ours — explicit so S3 presigned URLs see no chunked encoding. response = await client.put( url, content=aiter_io_chunks(tar_file), diff --git a/packages/python-sdk/e2b/template_async/main.py b/packages/python-sdk/e2b/template_async/main.py index 0a9c0e0374..f21f07f5a5 100644 --- a/packages/python-sdk/e2b/template_async/main.py +++ b/packages/python-sdk/e2b/template_async/main.py @@ -4,6 +4,7 @@ from typing_extensions import Unpack from e2b.api.client.client import AuthenticatedClient +from e2b.api.client.types import Unset from e2b.connection_config import ApiParams, ConnectionConfig from e2b.template.consts import GZIP, RESOLVE_SYMLINKS from e2b.template.logger import LogEntry, LogEntryEnd, LogEntryStart @@ -137,7 +138,11 @@ async def _build( resolve_symlinks, gzip, stack_trace, - headers=file_info.headers.to_dict() if file_info.headers else None, + headers=( + file_info.headers.to_dict() + if not isinstance(file_info.headers, Unset) + else None + ), request_timeout=request_timeout, ) if on_build_logs: diff --git a/packages/python-sdk/e2b/template_sync/build_api.py b/packages/python-sdk/e2b/template_sync/build_api.py index 0b956ff725..cf88608b7c 100644 --- a/packages/python-sdk/e2b/template_sync/build_api.py +++ b/packages/python-sdk/e2b/template_sync/build_api.py @@ -1,3 +1,4 @@ +import os import time from types import TracebackType from typing import Callable, Dict, Optional, List, Union @@ -113,6 +114,7 @@ def upload_file( resolve_symlinks: bool, gzip: bool, stack_trace: Optional[TracebackType], + *, headers: Optional[Dict[str, str]] = None, request_timeout: Optional[float] = None, ): @@ -128,6 +130,7 @@ def upload_file( tar_file = tar_file_stream( file_name, context_path, ignore_patterns, resolve_symlinks, gzip ) + size = os.fstat(tar_file.fileno()).st_size try: # Through the pyqwest adapter the upload timeout is a # whole-request deadline for the entire transfer, not a per-write @@ -149,13 +152,12 @@ def upload_file( ) ), ) as client: - # httpx streams the archive from disk in chunks and sets - # Content-Length from the file size—S3 presigned URLs reject - # chunked transfer encoding, and reqwest keeps the - # Content-Length framing for the streamed body. - # Headers the API asked for, applied as given (Azure's Put - # Blob requires x-ms-blob-type, which its SAS cannot carry). - response = client.put(url, content=tar_file, headers=headers) + # API-returned headers applied as given, but Content-Length stays ours — explicit so S3 presigned URLs see no chunked encoding. + response = client.put( + url, + content=tar_file, + headers={**(headers or {}), "Content-Length": str(size)}, + ) response.raise_for_status() finally: # Closing the spooled temp file is best-effort: a failure here diff --git a/packages/python-sdk/e2b/template_sync/main.py b/packages/python-sdk/e2b/template_sync/main.py index 55598cd749..7316422f0e 100644 --- a/packages/python-sdk/e2b/template_sync/main.py +++ b/packages/python-sdk/e2b/template_sync/main.py @@ -4,6 +4,7 @@ from typing_extensions import Unpack from e2b.api.client.client import AuthenticatedClient +from e2b.api.client.types import Unset from e2b.connection_config import ApiParams, ConnectionConfig from e2b.api.client_sync import get_api_client @@ -137,7 +138,11 @@ def _build( resolve_symlinks, gzip, stack_trace, - headers=file_info.headers.to_dict() if file_info.headers else None, + headers=( + file_info.headers.to_dict() + if not isinstance(file_info.headers, Unset) + else None + ), request_timeout=request_timeout, ) if on_build_logs: diff --git a/packages/python-sdk/tests/async/template_async/test_upload_file.py b/packages/python-sdk/tests/async/template_async/test_upload_file.py index 2274fc7fb9..0626a8bb44 100644 --- a/packages/python-sdk/tests/async/template_async/test_upload_file.py +++ b/packages/python-sdk/tests/async/template_async/test_upload_file.py @@ -243,9 +243,7 @@ def failing_close_stream(*args, **kwargs): async def test_upload_file_sends_the_headers_the_api_returned(tmp_path): - # Azure's Put Blob rejects the request without x-ms-blob-type, and its SAS - # cannot carry a required request header, so the API hands it back with the - # upload link for the client to apply. + # Azure's Put Blob needs a request header a SAS cannot carry, so the API hands it back with the upload link. (tmp_path / "hello.txt").write_text("hello world") server, thread, state = _make_server() @@ -273,8 +271,7 @@ async def test_upload_file_sends_the_headers_the_api_returned(tmp_path): async def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): - # S3 and GCS presigned PUTs sign the header set, so the upload must add - # nothing the API did not ask for. + # S3/GCS presigned PUTs sign the header set — the upload must add nothing the API did not ask for. (tmp_path / "hello.txt").write_text("hello world") server, thread, state = _make_server() @@ -298,3 +295,32 @@ async def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): thread.join(timeout=5) assert "x-ms-blob-type" not in state["headers"] + + +async def test_upload_file_keeps_its_own_content_length(tmp_path): + # An API-returned Content-Length must never override the real archive size. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + await upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + headers={"x-ms-blob-type": "BlockBlob", "Content-Length": "1"}, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert state["body_length"] > 1 + assert int(state["headers"]["content-length"]) == state["body_length"] diff --git a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py index 617ce8a304..d74acf813b 100644 --- a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py +++ b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py @@ -239,9 +239,7 @@ def failing_close_stream(*args, **kwargs): def test_upload_file_sends_the_headers_the_api_returned(tmp_path): - # Azure's Put Blob rejects the request without x-ms-blob-type, and its SAS - # cannot carry a required request header, so the API hands it back with the - # upload link for the client to apply. + # Azure's Put Blob needs a request header a SAS cannot carry, so the API hands it back with the upload link. (tmp_path / "hello.txt").write_text("hello world") server, thread, state = _make_server() @@ -269,8 +267,7 @@ def test_upload_file_sends_the_headers_the_api_returned(tmp_path): def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): - # S3 and GCS presigned PUTs sign the header set, so the upload must add - # nothing the API did not ask for. + # S3/GCS presigned PUTs sign the header set — the upload must add nothing the API did not ask for. (tmp_path / "hello.txt").write_text("hello world") server, thread, state = _make_server() @@ -294,3 +291,32 @@ def test_upload_file_adds_no_headers_when_the_api_returns_none(tmp_path): thread.join(timeout=5) assert "x-ms-blob-type" not in state["headers"] + + +def test_upload_file_keeps_its_own_content_length(tmp_path): + # An API-returned Content-Length must never override the real archive size. + (tmp_path / "hello.txt").write_text("hello world") + + server, thread, state = _make_server() + host, port = server.server_address + + try: + client = AuthenticatedClient(base_url="http://test", token="test") + upload_file( + api_client=client, + file_name="*.txt", + context_path=str(tmp_path), + url=f"http://{host}:{port}/upload", + ignore_patterns=[], + resolve_symlinks=False, + gzip=True, + stack_trace=None, + headers={"x-ms-blob-type": "BlockBlob", "Content-Length": "1"}, + ) + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + assert state["body_length"] > 1 + assert int(state["headers"]["content-length"]) == state["body_length"] From 5d27b1f29599caf5f7560ab3142da17ad9945e33 Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Mon, 14 Sep 2026 10:08:18 +0200 Subject: [PATCH 3/6] chore: re-pin spec/runtime-ref to the current runtime branch head and regen Mechanical: make codegen after the runtime branch merged its main (drags in the V1 template-build endpoint removal). Re-point at the merge commit before undrafting. Co-Authored-By: Claude Fable 5 --- .../client/api/templates/post_templates.py | 184 -------------- .../templates/post_templates_template_id.py | 193 --------------- ...t_templates_template_id_builds_build_id.py | 178 -------------- .../client/api/templates/post_v2_templates.py | 184 -------------- ...2_templates_template_id_builds_build_id.py | 16 +- .../e2b/api/client/models/__init__.py | 6 - .../api/client/models/template_build_info.py | 2 +- .../client/models/template_build_request.py | 115 --------- .../models/template_build_request_v2.py | 88 ------- .../client/models/template_build_start_v2.py | 3 +- .../e2b/api/client/models/template_legacy.py | 207 ---------------- spec/openapi.yml | 228 +----------------- spec/runtime-ref | 2 +- 13 files changed, 23 insertions(+), 1383 deletions(-) delete mode 100644 packages/python-sdk/e2b/api/client/api/templates/post_templates.py delete mode 100644 packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py delete mode 100644 packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py delete mode 100644 packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py delete mode 100644 packages/python-sdk/e2b/api/client/models/template_build_request.py delete mode 100644 packages/python-sdk/e2b/api/client/models/template_build_request_v2.py delete mode 100644 packages/python-sdk/e2b/api/client/models/template_legacy.py diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates.py deleted file mode 100644 index 61f4f7b73e..0000000000 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates.py +++ /dev/null @@ -1,184 +0,0 @@ -from http import HTTPStatus -from typing import Any, Optional, Union - -import httpx - -from ... import errors -from ...client import AuthenticatedClient, Client -from ...models.error import Error -from ...models.template_build_request import TemplateBuildRequest -from ...models.template_legacy import TemplateLegacy -from ...types import Response - - -def _get_kwargs( - *, - body: TemplateBuildRequest, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/templates", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Union[Error, TemplateLegacy]]: - if response.status_code == 202: - response_202 = TemplateLegacy.from_dict(response.json()) - - return response_202 - if response.status_code == 400: - response_400 = Error.from_dict(response.json()) - - return response_400 - if response.status_code == 401: - response_401 = Error.from_dict(response.json()) - - return response_401 - if response.status_code == 409: - response_409 = Error.from_dict(response.json()) - - return response_409 - if response.status_code == 500: - response_500 = Error.from_dict(response.json()) - - return response_500 - if client.raise_on_unexpected_status: - raise errors.UnexpectedStatus(response.status_code, response.content) - else: - return None - - -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Union[Error, TemplateLegacy]]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Response[Union[Error, TemplateLegacy]]: - """Create template - - Create a new template - - Args: - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Optional[Union[Error, TemplateLegacy]]: - """Create template - - Create a new template - - Args: - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return sync_detailed( - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Response[Union[Error, TemplateLegacy]]: - """Create template - - Create a new template - - Args: - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Optional[Union[Error, TemplateLegacy]]: - """Create template - - Create a new template - - Args: - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return ( - await asyncio_detailed( - client=client, - body=body, - ) - ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py deleted file mode 100644 index 35e68d3ddb..0000000000 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id.py +++ /dev/null @@ -1,193 +0,0 @@ -from http import HTTPStatus -from typing import Any, Optional, Union - -import httpx - -from ... import errors -from ...client import AuthenticatedClient, Client -from ...models.error import Error -from ...models.template_build_request import TemplateBuildRequest -from ...models.template_legacy import TemplateLegacy -from ...types import Response - - -def _get_kwargs( - template_id: str, - *, - body: TemplateBuildRequest, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": f"/templates/{template_id}", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Union[Error, TemplateLegacy]]: - if response.status_code == 202: - response_202 = TemplateLegacy.from_dict(response.json()) - - return response_202 - if response.status_code == 401: - response_401 = Error.from_dict(response.json()) - - return response_401 - if response.status_code == 409: - response_409 = Error.from_dict(response.json()) - - return response_409 - if response.status_code == 500: - response_500 = Error.from_dict(response.json()) - - return response_500 - if client.raise_on_unexpected_status: - raise errors.UnexpectedStatus(response.status_code, response.content) - else: - return None - - -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Union[Error, TemplateLegacy]]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - template_id: str, - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Response[Union[Error, TemplateLegacy]]: - """Rebuild template - - Rebuild an template - - Args: - template_id (str): - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - template_id=template_id, - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - template_id: str, - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Optional[Union[Error, TemplateLegacy]]: - """Rebuild template - - Rebuild an template - - Args: - template_id (str): - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return sync_detailed( - template_id=template_id, - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - template_id: str, - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Response[Union[Error, TemplateLegacy]]: - """Rebuild template - - Rebuild an template - - Args: - template_id (str): - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - template_id=template_id, - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - template_id: str, - *, - client: AuthenticatedClient, - body: TemplateBuildRequest, -) -> Optional[Union[Error, TemplateLegacy]]: - """Rebuild template - - Rebuild an template - - Args: - template_id (str): - body (TemplateBuildRequest): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return ( - await asyncio_detailed( - template_id=template_id, - client=client, - body=body, - ) - ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py deleted file mode 100644 index bab91c658f..0000000000 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py +++ /dev/null @@ -1,178 +0,0 @@ -from http import HTTPStatus -from typing import Any, Optional, Union, cast - -import httpx - -from ... import errors -from ...client import AuthenticatedClient, Client -from ...models.error import Error -from ...types import Response - - -def _get_kwargs( - template_id: str, - build_id: str, -) -> dict[str, Any]: - _kwargs: dict[str, Any] = { - "method": "post", - "url": f"/templates/{template_id}/builds/{build_id}", - } - - return _kwargs - - -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Union[Any, Error]]: - if response.status_code == 202: - response_202 = cast(Any, None) - return response_202 - if response.status_code == 401: - response_401 = Error.from_dict(response.json()) - - return response_401 - if response.status_code == 500: - response_500 = Error.from_dict(response.json()) - - return response_500 - if client.raise_on_unexpected_status: - raise errors.UnexpectedStatus(response.status_code, response.content) - else: - return None - - -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Union[Any, Error]]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - template_id: str, - build_id: str, - *, - client: AuthenticatedClient, -) -> Response[Union[Any, Error]]: - """Start template build - - Start the build - - Args: - template_id (str): - build_id (str): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Any, Error]] - """ - - kwargs = _get_kwargs( - template_id=template_id, - build_id=build_id, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - template_id: str, - build_id: str, - *, - client: AuthenticatedClient, -) -> Optional[Union[Any, Error]]: - """Start template build - - Start the build - - Args: - template_id (str): - build_id (str): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Any, Error] - """ - - return sync_detailed( - template_id=template_id, - build_id=build_id, - client=client, - ).parsed - - -async def asyncio_detailed( - template_id: str, - build_id: str, - *, - client: AuthenticatedClient, -) -> Response[Union[Any, Error]]: - """Start template build - - Start the build - - Args: - template_id (str): - build_id (str): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Any, Error]] - """ - - kwargs = _get_kwargs( - template_id=template_id, - build_id=build_id, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - template_id: str, - build_id: str, - *, - client: AuthenticatedClient, -) -> Optional[Union[Any, Error]]: - """Start template build - - Start the build - - Args: - template_id (str): - build_id (str): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Any, Error] - """ - - return ( - await asyncio_detailed( - template_id=template_id, - build_id=build_id, - client=client, - ) - ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py deleted file mode 100644 index 0ed4b6d706..0000000000 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v2_templates.py +++ /dev/null @@ -1,184 +0,0 @@ -from http import HTTPStatus -from typing import Any, Optional, Union - -import httpx - -from ... import errors -from ...client import AuthenticatedClient, Client -from ...models.error import Error -from ...models.template_build_request_v2 import TemplateBuildRequestV2 -from ...models.template_legacy import TemplateLegacy -from ...types import Response - - -def _get_kwargs( - *, - body: TemplateBuildRequestV2, -) -> dict[str, Any]: - headers: dict[str, Any] = {} - - _kwargs: dict[str, Any] = { - "method": "post", - "url": "/v2/templates", - } - - _kwargs["json"] = body.to_dict() - - headers["Content-Type"] = "application/json" - - _kwargs["headers"] = headers - return _kwargs - - -def _parse_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Optional[Union[Error, TemplateLegacy]]: - if response.status_code == 202: - response_202 = TemplateLegacy.from_dict(response.json()) - - return response_202 - if response.status_code == 400: - response_400 = Error.from_dict(response.json()) - - return response_400 - if response.status_code == 401: - response_401 = Error.from_dict(response.json()) - - return response_401 - if response.status_code == 409: - response_409 = Error.from_dict(response.json()) - - return response_409 - if response.status_code == 500: - response_500 = Error.from_dict(response.json()) - - return response_500 - if client.raise_on_unexpected_status: - raise errors.UnexpectedStatus(response.status_code, response.content) - else: - return None - - -def _build_response( - *, client: Union[AuthenticatedClient, Client], response: httpx.Response -) -> Response[Union[Error, TemplateLegacy]]: - return Response( - status_code=HTTPStatus(response.status_code), - content=response.content, - headers=response.headers, - parsed=_parse_response(client=client, response=response), - ) - - -def sync_detailed( - *, - client: AuthenticatedClient, - body: TemplateBuildRequestV2, -) -> Response[Union[Error, TemplateLegacy]]: - """Create template (v2) - - Create a new template - - Args: - body (TemplateBuildRequestV2): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = client.get_httpx_client().request( - **kwargs, - ) - - return _build_response(client=client, response=response) - - -def sync( - *, - client: AuthenticatedClient, - body: TemplateBuildRequestV2, -) -> Optional[Union[Error, TemplateLegacy]]: - """Create template (v2) - - Create a new template - - Args: - body (TemplateBuildRequestV2): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return sync_detailed( - client=client, - body=body, - ).parsed - - -async def asyncio_detailed( - *, - client: AuthenticatedClient, - body: TemplateBuildRequestV2, -) -> Response[Union[Error, TemplateLegacy]]: - """Create template (v2) - - Create a new template - - Args: - body (TemplateBuildRequestV2): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Response[Union[Error, TemplateLegacy]] - """ - - kwargs = _get_kwargs( - body=body, - ) - - response = await client.get_async_httpx_client().request(**kwargs) - - return _build_response(client=client, response=response) - - -async def asyncio( - *, - client: AuthenticatedClient, - body: TemplateBuildRequestV2, -) -> Optional[Union[Error, TemplateLegacy]]: - """Create template (v2) - - Create a new template - - Args: - body (TemplateBuildRequestV2): - - Raises: - errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. - httpx.TimeoutException: If the request takes longer than Client.timeout. - - Returns: - Union[Error, TemplateLegacy] - """ - - return ( - await asyncio_detailed( - client=client, - body=body, - ) - ).parsed diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py index 5ec52d489f..a67c6bdf4c 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py @@ -37,6 +37,10 @@ def _parse_response( if response.status_code == 202: response_202 = cast(Any, None) return response_202 + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + return response_400 if response.status_code == 401: response_401 = Error.from_dict(response.json()) @@ -76,7 +80,8 @@ def sync_detailed( Args: template_id (str): build_id (str): - body (TemplateBuildStartV2): + body (TemplateBuildStartV2): Exactly one of fromImage or fromTemplate must be given and + non-empty. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -113,7 +118,8 @@ def sync( Args: template_id (str): build_id (str): - body (TemplateBuildStartV2): + body (TemplateBuildStartV2): Exactly one of fromImage or fromTemplate must be given and + non-empty. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -145,7 +151,8 @@ async def asyncio_detailed( Args: template_id (str): build_id (str): - body (TemplateBuildStartV2): + body (TemplateBuildStartV2): Exactly one of fromImage or fromTemplate must be given and + non-empty. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -180,7 +187,8 @@ async def asyncio( Args: template_id (str): build_id (str): - body (TemplateBuildStartV2): + body (TemplateBuildStartV2): Exactly one of fromImage or fromTemplate must be given and + non-empty. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index 8390964fc1..3c1f982e4f 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -66,12 +66,9 @@ from .template_build_file_upload_headers import TemplateBuildFileUploadHeaders from .template_build_info import TemplateBuildInfo from .template_build_logs_response import TemplateBuildLogsResponse -from .template_build_request import TemplateBuildRequest -from .template_build_request_v2 import TemplateBuildRequestV2 from .template_build_request_v3 import TemplateBuildRequestV3 from .template_build_start_v2 import TemplateBuildStartV2 from .template_build_status import TemplateBuildStatus -from .template_legacy import TemplateLegacy from .template_request_response_v3 import TemplateRequestResponseV3 from .template_step import TemplateStep from .template_tag import TemplateTag @@ -148,12 +145,9 @@ "TemplateBuildFileUploadHeaders", "TemplateBuildInfo", "TemplateBuildLogsResponse", - "TemplateBuildRequest", - "TemplateBuildRequestV2", "TemplateBuildRequestV3", "TemplateBuildStartV2", "TemplateBuildStatus", - "TemplateLegacy", "TemplateRequestResponseV3", "TemplateStep", "TemplateTag", diff --git a/packages/python-sdk/e2b/api/client/models/template_build_info.py b/packages/python-sdk/e2b/api/client/models/template_build_info.py index 91e1d3fbdb..3019ae860d 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build_info.py +++ b/packages/python-sdk/e2b/api/client/models/template_build_info.py @@ -19,7 +19,7 @@ class TemplateBuildInfo: """ Attributes: - logs (list[str]): Build logs + logs (list[str]): Build logs (always empty since the V1 build path was removed, use logEntries) log_entries (list['BuildLogEntry']): Build logs structured template_id (str): Identifier of the template build_id (str): Identifier of the build diff --git a/packages/python-sdk/e2b/api/client/models/template_build_request.py b/packages/python-sdk/e2b/api/client/models/template_build_request.py deleted file mode 100644 index b24df84463..0000000000 --- a/packages/python-sdk/e2b/api/client/models/template_build_request.py +++ /dev/null @@ -1,115 +0,0 @@ -from collections.abc import Mapping -from typing import Any, TypeVar, Union - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="TemplateBuildRequest") - - -@_attrs_define -class TemplateBuildRequest: - """ - Attributes: - dockerfile (str): Dockerfile for the template - alias (Union[Unset, str]): Alias of the template - team_id (Union[Unset, str]): Identifier of the team - start_cmd (Union[Unset, str]): Start command to execute in the template after the build - ready_cmd (Union[Unset, str]): Ready check command to execute in the template after the build - cpu_count (Union[Unset, int]): CPU cores for the sandbox - memory_mb (Union[Unset, int]): Memory for the sandbox in MiB - """ - - dockerfile: str - alias: Union[Unset, str] = UNSET - team_id: Union[Unset, str] = UNSET - start_cmd: Union[Unset, str] = UNSET - ready_cmd: Union[Unset, str] = UNSET - cpu_count: Union[Unset, int] = UNSET - memory_mb: Union[Unset, int] = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - dockerfile = self.dockerfile - - alias = self.alias - - team_id = self.team_id - - start_cmd = self.start_cmd - - ready_cmd = self.ready_cmd - - cpu_count = self.cpu_count - - memory_mb = self.memory_mb - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "dockerfile": dockerfile, - } - ) - if alias is not UNSET: - field_dict["alias"] = alias - if team_id is not UNSET: - field_dict["teamID"] = team_id - if start_cmd is not UNSET: - field_dict["startCmd"] = start_cmd - if ready_cmd is not UNSET: - field_dict["readyCmd"] = ready_cmd - if cpu_count is not UNSET: - field_dict["cpuCount"] = cpu_count - if memory_mb is not UNSET: - field_dict["memoryMB"] = memory_mb - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - dockerfile = d.pop("dockerfile") - - alias = d.pop("alias", UNSET) - - team_id = d.pop("teamID", UNSET) - - start_cmd = d.pop("startCmd", UNSET) - - ready_cmd = d.pop("readyCmd", UNSET) - - cpu_count = d.pop("cpuCount", UNSET) - - memory_mb = d.pop("memoryMB", UNSET) - - template_build_request = cls( - dockerfile=dockerfile, - alias=alias, - team_id=team_id, - start_cmd=start_cmd, - ready_cmd=ready_cmd, - cpu_count=cpu_count, - memory_mb=memory_mb, - ) - - template_build_request.additional_properties = d - return template_build_request - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/template_build_request_v2.py b/packages/python-sdk/e2b/api/client/models/template_build_request_v2.py deleted file mode 100644 index 1194f49c0b..0000000000 --- a/packages/python-sdk/e2b/api/client/models/template_build_request_v2.py +++ /dev/null @@ -1,88 +0,0 @@ -from collections.abc import Mapping -from typing import Any, TypeVar, Union - -from attrs import define as _attrs_define -from attrs import field as _attrs_field - -from ..types import UNSET, Unset - -T = TypeVar("T", bound="TemplateBuildRequestV2") - - -@_attrs_define -class TemplateBuildRequestV2: - """ - Attributes: - alias (str): Alias of the template - team_id (Union[Unset, str]): Identifier of the team - cpu_count (Union[Unset, int]): CPU cores for the sandbox - memory_mb (Union[Unset, int]): Memory for the sandbox in MiB - """ - - alias: str - team_id: Union[Unset, str] = UNSET - cpu_count: Union[Unset, int] = UNSET - memory_mb: Union[Unset, int] = UNSET - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - alias = self.alias - - team_id = self.team_id - - cpu_count = self.cpu_count - - memory_mb = self.memory_mb - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "alias": alias, - } - ) - if team_id is not UNSET: - field_dict["teamID"] = team_id - if cpu_count is not UNSET: - field_dict["cpuCount"] = cpu_count - if memory_mb is not UNSET: - field_dict["memoryMB"] = memory_mb - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - d = dict(src_dict) - alias = d.pop("alias") - - team_id = d.pop("teamID", UNSET) - - cpu_count = d.pop("cpuCount", UNSET) - - memory_mb = d.pop("memoryMB", UNSET) - - template_build_request_v2 = cls( - alias=alias, - team_id=team_id, - cpu_count=cpu_count, - memory_mb=memory_mb, - ) - - template_build_request_v2.additional_properties = d - return template_build_request_v2 - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/template_build_start_v2.py b/packages/python-sdk/e2b/api/client/models/template_build_start_v2.py index d2bbaac1cd..538e9a017f 100644 --- a/packages/python-sdk/e2b/api/client/models/template_build_start_v2.py +++ b/packages/python-sdk/e2b/api/client/models/template_build_start_v2.py @@ -18,7 +18,8 @@ @_attrs_define class TemplateBuildStartV2: - """ + """Exactly one of fromImage or fromTemplate must be given and non-empty. + Attributes: from_image (Union[Unset, str]): Image to use as a base for the template build from_template (Union[Unset, str]): Template to use as a base for the template build diff --git a/packages/python-sdk/e2b/api/client/models/template_legacy.py b/packages/python-sdk/e2b/api/client/models/template_legacy.py deleted file mode 100644 index e78f50748f..0000000000 --- a/packages/python-sdk/e2b/api/client/models/template_legacy.py +++ /dev/null @@ -1,207 +0,0 @@ -import datetime -from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, TypeVar, Union, cast - -from attrs import define as _attrs_define -from attrs import field as _attrs_field -from dateutil.parser import isoparse - -if TYPE_CHECKING: - from ..models.team_user import TeamUser - - -T = TypeVar("T", bound="TemplateLegacy") - - -@_attrs_define -class TemplateLegacy: - """ - Attributes: - template_id (str): Identifier of the template - build_id (str): Identifier of the last successful build for given template - cpu_count (int): CPU cores for the sandbox - memory_mb (int): Memory for the sandbox in MiB - disk_size_mb (int): Disk size for the sandbox in MiB - public (bool): Whether the template is public or only accessible by the team - aliases (list[str]): Aliases of the template - created_at (datetime.datetime): Time when the template was created - updated_at (datetime.datetime): Time when the template was last updated - created_by (Union['TeamUser', None]): - last_spawned_at (Union[None, datetime.datetime]): Time when the template was last used - spawn_count (int): Number of times the template was used - build_count (int): Number of times the template was built - envd_version (str): Version of the envd running in the sandbox - """ - - template_id: str - build_id: str - cpu_count: int - memory_mb: int - disk_size_mb: int - public: bool - aliases: list[str] - created_at: datetime.datetime - updated_at: datetime.datetime - created_by: Union["TeamUser", None] - last_spawned_at: Union[None, datetime.datetime] - spawn_count: int - build_count: int - envd_version: str - additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) - - def to_dict(self) -> dict[str, Any]: - from ..models.team_user import TeamUser - - template_id = self.template_id - - build_id = self.build_id - - cpu_count = self.cpu_count - - memory_mb = self.memory_mb - - disk_size_mb = self.disk_size_mb - - public = self.public - - aliases = self.aliases - - created_at = self.created_at.isoformat() - - updated_at = self.updated_at.isoformat() - - created_by: Union[None, dict[str, Any]] - if isinstance(self.created_by, TeamUser): - created_by = self.created_by.to_dict() - else: - created_by = self.created_by - - last_spawned_at: Union[None, str] - if isinstance(self.last_spawned_at, datetime.datetime): - last_spawned_at = self.last_spawned_at.isoformat() - else: - last_spawned_at = self.last_spawned_at - - spawn_count = self.spawn_count - - build_count = self.build_count - - envd_version = self.envd_version - - field_dict: dict[str, Any] = {} - field_dict.update(self.additional_properties) - field_dict.update( - { - "templateID": template_id, - "buildID": build_id, - "cpuCount": cpu_count, - "memoryMB": memory_mb, - "diskSizeMB": disk_size_mb, - "public": public, - "aliases": aliases, - "createdAt": created_at, - "updatedAt": updated_at, - "createdBy": created_by, - "lastSpawnedAt": last_spawned_at, - "spawnCount": spawn_count, - "buildCount": build_count, - "envdVersion": envd_version, - } - ) - - return field_dict - - @classmethod - def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - from ..models.team_user import TeamUser - - d = dict(src_dict) - template_id = d.pop("templateID") - - build_id = d.pop("buildID") - - cpu_count = d.pop("cpuCount") - - memory_mb = d.pop("memoryMB") - - disk_size_mb = d.pop("diskSizeMB") - - public = d.pop("public") - - aliases = cast(list[str], d.pop("aliases")) - - created_at = isoparse(d.pop("createdAt")) - - updated_at = isoparse(d.pop("updatedAt")) - - def _parse_created_by(data: object) -> Union["TeamUser", None]: - if data is None: - return data - try: - if not isinstance(data, dict): - raise TypeError() - created_by_type_1 = TeamUser.from_dict(data) - - return created_by_type_1 - except: # noqa: E722 - pass - return cast(Union["TeamUser", None], data) - - created_by = _parse_created_by(d.pop("createdBy")) - - def _parse_last_spawned_at(data: object) -> Union[None, datetime.datetime]: - if data is None: - return data - try: - if not isinstance(data, str): - raise TypeError() - last_spawned_at_type_0 = isoparse(data) - - return last_spawned_at_type_0 - except: # noqa: E722 - pass - return cast(Union[None, datetime.datetime], data) - - last_spawned_at = _parse_last_spawned_at(d.pop("lastSpawnedAt")) - - spawn_count = d.pop("spawnCount") - - build_count = d.pop("buildCount") - - envd_version = d.pop("envdVersion") - - template_legacy = cls( - template_id=template_id, - build_id=build_id, - cpu_count=cpu_count, - memory_mb=memory_mb, - disk_size_mb=disk_size_mb, - public=public, - aliases=aliases, - created_at=created_at, - updated_at=updated_at, - created_by=created_by, - last_spawned_at=last_spawned_at, - spawn_count=spawn_count, - build_count=build_count, - envd_version=envd_version, - ) - - template_legacy.additional_properties = d - return template_legacy - - @property - def additional_keys(self) -> list[str]: - return list(self.additional_properties.keys()) - - def __getitem__(self, key: str) -> Any: - return self.additional_properties[key] - - def __setitem__(self, key: str, value: Any) -> None: - self.additional_properties[key] = value - - def __delitem__(self, key: str) -> None: - del self.additional_properties[key] - - def __contains__(self, key: str) -> bool: - return key in self.additional_properties diff --git a/spec/openapi.yml b/spec/openapi.yml index 6df979f231..748aeecb16 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -1293,71 +1293,6 @@ components: items: type: string - TemplateLegacy: - required: - - templateID - - buildID - - cpuCount - - memoryMB - - diskSizeMB - - public - - createdAt - - updatedAt - - createdBy - - lastSpawnedAt - - spawnCount - - buildCount - - envdVersion - - aliases - properties: - templateID: - type: string - description: Identifier of the template - buildID: - type: string - description: Identifier of the last successful build for given template - cpuCount: - $ref: "#/components/schemas/CPUCount" - memoryMB: - $ref: "#/components/schemas/MemoryMB" - diskSizeMB: - $ref: "#/components/schemas/DiskSizeMB" - public: - type: boolean - description: Whether the template is public or only accessible by the team - aliases: - type: array - description: Aliases of the template - items: - type: string - createdAt: - type: string - format: date-time - description: Time when the template was created - updatedAt: - type: string - format: date-time - description: Time when the template was last updated - createdBy: - allOf: - - $ref: "#/components/schemas/TeamUser" - nullable: true - lastSpawnedAt: - type: string - nullable: true - format: date-time - description: Time when the template was last used - spawnCount: - type: integer - format: int64 - description: Number of times the template was used - buildCount: - type: integer - format: int32 - description: Number of times the template was built - envdVersion: - $ref: "#/components/schemas/EnvdVersion" - TemplateBuild: required: - buildID @@ -1458,30 +1393,6 @@ components: type: boolean description: Whether the template is public or only accessible by the team - TemplateBuildRequest: - required: - - dockerfile - properties: - alias: - description: Alias of the template - type: string - dockerfile: - description: Dockerfile for the template - type: string - teamID: - type: string - description: Identifier of the team - startCmd: - description: Start command to execute in the template after the build - type: string - readyCmd: - description: Ready check command to execute in the template after the build - type: string - cpuCount: - $ref: "#/components/schemas/CPUCount" - memoryMB: - $ref: "#/components/schemas/MemoryMB" - TemplateStep: description: Step in the template build process required: @@ -1531,22 +1442,6 @@ components: minFreeDiskMb: $ref: "#/components/schemas/MinFreeDiskMb" - TemplateBuildRequestV2: - required: - - alias - properties: - alias: - description: Alias of the template - type: string - teamID: - deprecated: true - type: string - description: Identifier of the team - cpuCount: - $ref: "#/components/schemas/CPUCount" - memoryMB: - $ref: "#/components/schemas/MemoryMB" - FromImageRegistry: oneOf: - $ref: "#/components/schemas/AWSRegistry" @@ -1615,12 +1510,15 @@ components: TemplateBuildStartV2: type: object + description: Exactly one of fromImage or fromTemplate must be given and non-empty. properties: fromImage: type: string + minLength: 1 description: Image to use as a base for the template build fromTemplate: type: string + minLength: 1 description: Template to use as a base for the template build fromImageRegistry: $ref: "#/components/schemas/FromImageRegistry" @@ -1721,7 +1619,8 @@ components: properties: logs: default: [] - description: Build logs + deprecated: true + description: Build logs (always empty since the V1 build path was removed, use logEntries) type: array items: type: string @@ -3303,41 +3202,6 @@ paths: $ref: "#/components/responses/403" "500": $ref: "#/components/responses/500" - post: - summary: Create template (v2) - description: Create a new template - deprecated: true - tags: [templates] - security: - - ApiKeyAuth: [] - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - - AdminApiKeyAuth: [] - AdminTeamAuth: [] - - AdminJWTAuth: [] - AdminTeamAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateBuildRequestV2" - - responses: - "202": - description: The build was requested successfully - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateLegacy" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" /templates/{templateID}/files/{hash}: get: @@ -3411,36 +3275,6 @@ paths: $ref: "#/components/responses/401" "500": $ref: "#/components/responses/500" - post: - summary: Create template - description: Create a new template - deprecated: true - tags: [templates] - security: - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateBuildRequest" - - responses: - "202": - description: The build was accepted - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateLegacy" - "400": - $ref: "#/components/responses/400" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" /templates/{templateID}: get: @@ -3473,36 +3307,6 @@ paths: $ref: "#/components/responses/401" "500": $ref: "#/components/responses/500" - post: - summary: Rebuild template - description: Rebuild an template - deprecated: true - tags: [templates] - security: - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateBuildRequest" - - responses: - "202": - description: The build was accepted - content: - application/json: - schema: - $ref: "#/components/schemas/TemplateLegacy" - "401": - $ref: "#/components/responses/401" - "409": - $ref: "#/components/responses/409" - "500": - $ref: "#/components/responses/500" delete: summary: Delete template description: Delete a template @@ -3555,26 +3359,6 @@ paths: "500": $ref: "#/components/responses/500" - /templates/{templateID}/builds/{buildID}: - post: - summary: Start template build - description: Start the build - deprecated: true - tags: [templates] - security: - - AuthProviderBearerAuth: [] - AuthProviderTeamAuth: [] - parameters: - - $ref: "#/components/parameters/templateID" - - $ref: "#/components/parameters/buildID" - responses: - "202": - description: The build has started - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - /v2/templates/{templateID}/builds/{buildID}: post: summary: Start template build (v2) @@ -3600,6 +3384,8 @@ paths: responses: "202": description: The build has started + "400": + $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" "500": diff --git a/spec/runtime-ref b/spec/runtime-ref index 3809a41f0c..d508db9524 100644 --- a/spec/runtime-ref +++ b/spec/runtime-ref @@ -1 +1 @@ -8cd25be70e2d9e6f9bb457c15fdad51b55bd4bdf +eee804e37a7a0e61b1be1e4dc8d89359f124798c From bfc487de9596f8e4de8b368dbe9bab9537bbfba5 Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Mon, 14 Sep 2026 10:50:05 +0200 Subject: [PATCH 4/6] fix(sdk): strip API-sent Content-Length case-insensitively before setting ours Review finding: fetch header names are case-insensitive but object/dict keys are not, so an API-returned lowercase content-length survived the spread and undici/httpx would send both values. Filter it out in JS and both Python variants; the keeps-its-own-Content-Length tests now use the lowercase spelling to force the path. Co-Authored-By: Claude Fable 5 --- packages/js-sdk/src/template/buildApi.ts | 8 ++++++-- packages/js-sdk/tests/template/uploadFile.test.ts | 3 ++- packages/python-sdk/e2b/template_async/build_api.py | 9 ++++++++- packages/python-sdk/e2b/template_sync/build_api.py | 9 ++++++++- .../tests/async/template_async/test_upload_file.py | 3 ++- .../tests/sync/template_sync/test_upload_file.py | 3 ++- 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/js-sdk/src/template/buildApi.ts b/packages/js-sdk/src/template/buildApi.ts index 8c160b4e2d..5b8aba70ea 100644 --- a/packages/js-sdk/src/template/buildApi.ts +++ b/packages/js-sdk/src/template/buildApi.ts @@ -195,9 +195,13 @@ async function putFileStream( body: stream.Readable.toWeb( fs.createReadStream(filePath) ) as ReadableStream, - // API-returned headers applied as given (Azure needs x-ms-blob-type, which a SAS cannot carry); Content-Length stays ours. + // API-returned headers applied as given (Azure needs x-ms-blob-type, which a SAS cannot carry); Content-Length stays ours, dropped case-insensitively since fetch header names are not case-sensitive. headers: { - ...headers, + ...Object.fromEntries( + Object.entries(headers ?? {}).filter( + ([name]) => name.toLowerCase() !== 'content-length' + ) + ), 'Content-Length': size.toString(), }, // Streaming request bodies require half-duplex mode. diff --git a/packages/js-sdk/tests/template/uploadFile.test.ts b/packages/js-sdk/tests/template/uploadFile.test.ts index 2fb68fdc2e..fe4b59124c 100644 --- a/packages/js-sdk/tests/template/uploadFile.test.ts +++ b/packages/js-sdk/tests/template/uploadFile.test.ts @@ -102,7 +102,8 @@ describe('uploadFile transfer encoding', () => { fileName: '*.txt', fileContextPath: testDir, url: baseUrl, - headers: { 'Content-Length': '1' }, + // lowercase on purpose: header names are case-insensitive, object keys are not + headers: { 'content-length': '1' }, ignorePatterns: [], resolveSymlinks: false, gzip: true, diff --git a/packages/python-sdk/e2b/template_async/build_api.py b/packages/python-sdk/e2b/template_async/build_api.py index 895e25e78d..8c8733b621 100644 --- a/packages/python-sdk/e2b/template_async/build_api.py +++ b/packages/python-sdk/e2b/template_async/build_api.py @@ -158,7 +158,14 @@ async def upload_file( response = await client.put( url, content=aiter_io_chunks(tar_file), - headers={**(headers or {}), "Content-Length": str(size)}, + headers={ + **{ + k: v + for k, v in (headers or {}).items() + if k.lower() != "content-length" + }, + "Content-Length": str(size), + }, ) response.raise_for_status() finally: diff --git a/packages/python-sdk/e2b/template_sync/build_api.py b/packages/python-sdk/e2b/template_sync/build_api.py index cf88608b7c..e931c03b38 100644 --- a/packages/python-sdk/e2b/template_sync/build_api.py +++ b/packages/python-sdk/e2b/template_sync/build_api.py @@ -156,7 +156,14 @@ def upload_file( response = client.put( url, content=tar_file, - headers={**(headers or {}), "Content-Length": str(size)}, + headers={ + **{ + k: v + for k, v in (headers or {}).items() + if k.lower() != "content-length" + }, + "Content-Length": str(size), + }, ) response.raise_for_status() finally: diff --git a/packages/python-sdk/tests/async/template_async/test_upload_file.py b/packages/python-sdk/tests/async/template_async/test_upload_file.py index 0626a8bb44..3754639f8b 100644 --- a/packages/python-sdk/tests/async/template_async/test_upload_file.py +++ b/packages/python-sdk/tests/async/template_async/test_upload_file.py @@ -315,7 +315,8 @@ async def test_upload_file_keeps_its_own_content_length(tmp_path): resolve_symlinks=False, gzip=True, stack_trace=None, - headers={"x-ms-blob-type": "BlockBlob", "Content-Length": "1"}, + # lowercase on purpose: header names are case-insensitive, dict keys are not + headers={"x-ms-blob-type": "BlockBlob", "content-length": "1"}, ) finally: server.shutdown() diff --git a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py index d74acf813b..52bbf7e527 100644 --- a/packages/python-sdk/tests/sync/template_sync/test_upload_file.py +++ b/packages/python-sdk/tests/sync/template_sync/test_upload_file.py @@ -311,7 +311,8 @@ def test_upload_file_keeps_its_own_content_length(tmp_path): resolve_symlinks=False, gzip=True, stack_trace=None, - headers={"x-ms-blob-type": "BlockBlob", "Content-Length": "1"}, + # lowercase on purpose: header names are case-insensitive, dict keys are not + headers={"x-ms-blob-type": "BlockBlob", "content-length": "1"}, ) finally: server.shutdown() From 1cfbb7c4d3a714769bac78fbc2a42467d75721c0 Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Mon, 14 Sep 2026 17:07:19 +0200 Subject: [PATCH 5/6] chore: re-point spec/runtime-ref at the merged runtime main and regen belt#3308 merged and synced out; the pin now references the contract on main (drags in the 429 declarations from the same window). Co-Authored-By: Claude Fable 5 --- packages/js-sdk/src/api/schema.gen.ts | 45 +++++++ .../sandboxes/delete_sandboxes_sandbox_id.py | 4 + .../api/client/api/sandboxes/get_sandboxes.py | 4 + .../api/sandboxes/get_sandboxes_metrics.py | 4 + .../api/sandboxes/get_sandboxes_sandbox_id.py | 4 + .../get_sandboxes_sandbox_id_logs.py | 4 + .../get_sandboxes_sandbox_id_metrics.py | 4 + .../client/api/sandboxes/get_v2_sandboxes.py | 4 + .../get_v_2_sandboxes_sandbox_id_logs.py | 4 + .../client/api/sandboxes/post_sandboxes.py | 4 + .../post_sandboxes_sandbox_id_connect.py | 4 + .../post_sandboxes_sandbox_id_fork.py | 4 + .../post_sandboxes_sandbox_id_pause.py | 4 + .../post_sandboxes_sandbox_id_refreshes.py | 4 + .../post_sandboxes_sandbox_id_resume.py | 4 + .../post_sandboxes_sandbox_id_snapshots.py | 4 + .../post_sandboxes_sandbox_id_timeout.py | 4 + .../put_sandboxes_sandbox_id_network.py | 4 + .../api/client/api/snapshots/get_snapshots.py | 4 + .../client/api/tags/delete_templates_tags.py | 4 + .../tags/get_templates_template_id_tags.py | 4 + .../client/api/tags/post_templates_tags.py | 4 + .../templates/delete_templates_template_id.py | 4 + .../api/client/api/templates/get_templates.py | 4 + .../templates/get_templates_aliases_alias.py | 4 + .../templates/get_templates_template_id.py | 4 + ...plates_template_id_builds_build_id_logs.py | 4 + ...ates_template_id_builds_build_id_status.py | 4 + .../get_templates_template_id_files_hash.py | 4 + .../client/api/templates/get_v2_templates.py | 4 + .../templates/patch_templates_template_id.py | 4 + .../patch_v_2_templates_template_id.py | 4 + .../client/api/templates/post_v3_templates.py | 4 + ...2_templates_template_id_builds_build_id.py | 4 + .../api/volumes/delete_volumes_volume_id.py | 4 + .../e2b/api/client/api/volumes/get_volumes.py | 4 + .../api/volumes/get_volumes_volume_id.py | 4 + .../api/client/api/volumes/post_volumes.py | 4 + spec/openapi.yml | 124 ++++++++++++++++++ spec/runtime-ref | 2 +- 40 files changed, 318 insertions(+), 1 deletion(-) diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 1f498dbd6c..15c75cc10f 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -39,6 +39,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -71,6 +72,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; 503: components["responses"]["503"]; 504: components["responses"]["504"]; @@ -115,6 +117,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -144,6 +147,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -202,6 +206,7 @@ export interface paths { 401: components["responses"]["401"]; 404: components["responses"]["404"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; 503: components["responses"]["503"]; 504: components["responses"]["504"]; @@ -253,6 +258,7 @@ export interface paths { 401: components["responses"]["401"]; 404: components["responses"]["404"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; 503: components["responses"]["503"]; }; @@ -302,6 +308,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -351,6 +358,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -399,6 +407,7 @@ export interface paths { 401: components["responses"]["401"]; 404: components["responses"]["404"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -447,6 +456,7 @@ export interface paths { 401: components["responses"]["401"]; 404: components["responses"]["404"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; 503: components["responses"]["503"]; }; @@ -494,6 +504,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; }; }; delete?: never; @@ -544,6 +555,7 @@ export interface paths { 401: components["responses"]["401"]; 404: components["responses"]["404"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; 503: components["responses"]["503"]; 504: components["responses"]["504"]; @@ -595,6 +607,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -641,6 +654,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -684,6 +698,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -944,6 +959,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -985,6 +1001,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1034,6 +1051,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 403: components["responses"]["403"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1085,6 +1103,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 403: components["responses"]["403"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1129,6 +1148,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1178,6 +1198,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1206,6 +1227,7 @@ export interface paths { content?: never; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1240,6 +1262,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1288,6 +1311,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1339,6 +1363,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1385,6 +1410,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1430,6 +1456,7 @@ export interface paths { 401: components["responses"]["401"]; 403: components["responses"]["403"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1475,6 +1502,7 @@ export interface paths { 400: components["responses"]["400"]; 403: components["responses"]["403"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1524,6 +1552,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1554,6 +1583,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1610,6 +1640,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1665,6 +1696,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1715,6 +1747,7 @@ export interface paths { 400: components["responses"]["400"]; 401: components["responses"]["401"]; 403: components["responses"]["403"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1769,6 +1802,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1812,6 +1846,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1860,6 +1895,7 @@ export interface paths { 401: components["responses"]["401"]; 403: components["responses"]["403"]; 409: components["responses"]["409"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1899,6 +1935,7 @@ export interface paths { }; }; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1931,6 +1968,7 @@ export interface paths { }; 400: components["responses"]["400"]; 401: components["responses"]["401"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -1973,6 +2011,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -2002,6 +2041,7 @@ export interface paths { }; 401: components["responses"]["401"]; 404: components["responses"]["404"]; + 429: components["responses"]["429"]; 500: components["responses"]["500"]; }; }; @@ -2964,6 +3004,11 @@ export interface components { /** @description Too many requests */ 429: { headers: { + /** + * @description When present, the number of seconds to wait before retrying the request. + * @example 30 + */ + "Retry-After"?: number; [name: string]: unknown; }; content: { diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py index cc3f9d3725..51a3271b5c 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py @@ -34,6 +34,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py index 750ed96e0c..0da7eab1e0 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes.py @@ -49,6 +49,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py index 5554bc76ce..623601079b 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_metrics.py @@ -46,6 +46,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py index 59781ab7b3..503120bc42 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id.py @@ -36,6 +36,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py index 49d13223ed..a9785561b5 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py @@ -48,6 +48,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py index 48343566d6..92c08c6a82 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py @@ -57,6 +57,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_v2_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_v2_sandboxes.py index 90ce4ce2d2..b4c6e19c1a 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_v2_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_v2_sandboxes.py @@ -85,6 +85,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py b/packages/python-sdk/e2b/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py index b3cc1dad17..0bc7487cd8 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py @@ -67,6 +67,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py index 56f5dc1557..5bce9d8b33 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py index b2562c449a..7ecdccf34f 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py @@ -58,6 +58,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_fork.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_fork.py index 9d09e939eb..fbb5cca4f6 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_fork.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_fork.py @@ -55,6 +55,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py index a145bc4d34..f0989b19e5 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py @@ -48,6 +48,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py index b9ed871273..78ece1e905 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py @@ -44,6 +44,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py index 6e91162587..7232c98b87 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py @@ -54,6 +54,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py index 83e17e5e62..058b41c30b 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py @@ -50,6 +50,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py index a472b05b33..4dbfc62dec 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py @@ -44,6 +44,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py b/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py index aefbc9e481..c64ffc077a 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py @@ -48,6 +48,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py index 3f200588f9..2bc10c71f1 100644 --- a/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py +++ b/packages/python-sdk/e2b/api/client/api/snapshots/get_snapshots.py @@ -54,6 +54,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/tags/delete_templates_tags.py b/packages/python-sdk/e2b/api/client/api/tags/delete_templates_tags.py index 6ab1f6d256..82f920b0c5 100644 --- a/packages/python-sdk/e2b/api/client/api/tags/delete_templates_tags.py +++ b/packages/python-sdk/e2b/api/client/api/tags/delete_templates_tags.py @@ -47,6 +47,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/tags/get_templates_template_id_tags.py b/packages/python-sdk/e2b/api/client/api/tags/get_templates_template_id_tags.py index 636906beee..1d290f1766 100644 --- a/packages/python-sdk/e2b/api/client/api/tags/get_templates_template_id_tags.py +++ b/packages/python-sdk/e2b/api/client/api/tags/get_templates_template_id_tags.py @@ -45,6 +45,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/tags/post_templates_tags.py b/packages/python-sdk/e2b/api/client/api/tags/post_templates_tags.py index ad36cfdc01..db1c09f3ba 100644 --- a/packages/python-sdk/e2b/api/client/api/tags/post_templates_tags.py +++ b/packages/python-sdk/e2b/api/client/api/tags/post_templates_tags.py @@ -49,6 +49,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py index 1cc2ea9536..7b83d84208 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py @@ -30,6 +30,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates.py index 97d2e17a10..de3c19a9a4 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates_aliases_alias.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates_aliases_alias.py index dcfe694054..956ddcce37 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates_aliases_alias.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates_aliases_alias.py @@ -40,6 +40,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id.py index 4444052175..d71c40693c 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id.py @@ -44,6 +44,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py index f396c62ba1..d1382bc1a3 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py @@ -73,6 +73,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_status.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_status.py index 07bb2d5edc..26e758ad65 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_builds_build_id_status.py @@ -57,6 +57,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_files_hash.py b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_files_hash.py index 959dbf41a9..834de12cf7 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_files_hash.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_templates_template_id_files_hash.py @@ -41,6 +41,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/get_v2_templates.py b/packages/python-sdk/e2b/api/client/api/templates/get_v2_templates.py index 915156e95a..d8a35069bc 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/get_v2_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/get_v2_templates.py @@ -59,6 +59,10 @@ def _parse_response( response_403 = Error.from_dict(response.json()) return response_403 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/patch_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/patch_templates_template_id.py index 7ea0a857cb..d57bc133fc 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/patch_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/patch_templates_template_id.py @@ -44,6 +44,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py index 699626d41b..e73acca7a1 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py @@ -46,6 +46,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py b/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py index 3ae4afbd29..4d49793032 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_v3_templates.py @@ -53,6 +53,10 @@ def _parse_response( response_409 = Error.from_dict(response.json()) return response_409 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py index a67c6bdf4c..d989aafd34 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/volumes/delete_volumes_volume_id.py b/packages/python-sdk/e2b/api/client/api/volumes/delete_volumes_volume_id.py index 250b859dfb..3f9f9191fd 100644 --- a/packages/python-sdk/e2b/api/client/api/volumes/delete_volumes_volume_id.py +++ b/packages/python-sdk/e2b/api/client/api/volumes/delete_volumes_volume_id.py @@ -34,6 +34,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/volumes/get_volumes.py b/packages/python-sdk/e2b/api/client/api/volumes/get_volumes.py index 375df68ee5..60f25e3f8a 100644 --- a/packages/python-sdk/e2b/api/client/api/volumes/get_volumes.py +++ b/packages/python-sdk/e2b/api/client/api/volumes/get_volumes.py @@ -35,6 +35,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/volumes/get_volumes_volume_id.py b/packages/python-sdk/e2b/api/client/api/volumes/get_volumes_volume_id.py index 9511c06704..8838d0bf04 100644 --- a/packages/python-sdk/e2b/api/client/api/volumes/get_volumes_volume_id.py +++ b/packages/python-sdk/e2b/api/client/api/volumes/get_volumes_volume_id.py @@ -36,6 +36,10 @@ def _parse_response( response_404 = Error.from_dict(response.json()) return response_404 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/packages/python-sdk/e2b/api/client/api/volumes/post_volumes.py b/packages/python-sdk/e2b/api/client/api/volumes/post_volumes.py index 8967e7366d..51f75f62c8 100644 --- a/packages/python-sdk/e2b/api/client/api/volumes/post_volumes.py +++ b/packages/python-sdk/e2b/api/client/api/volumes/post_volumes.py @@ -45,6 +45,10 @@ def _parse_response( response_401 = Error.from_dict(response.json()) return response_401 + if response.status_code == 429: + response_429 = Error.from_dict(response.json()) + + return response_429 if response.status_code == 500: response_500 = Error.from_dict(response.json()) diff --git a/spec/openapi.yml b/spec/openapi.yml index 748aeecb16..8c43992469 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -183,6 +183,14 @@ components: $ref: "#/components/schemas/Error" "429": description: Too many requests + headers: + Retry-After: + description: When present, the number of seconds to wait before retrying the request. + required: false + schema: + type: integer + minimum: 0 + example: 30 content: application/json: schema: @@ -2259,6 +2267,8 @@ paths: description: The service is healthy "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" /teams: get: @@ -2278,6 +2288,8 @@ paths: $ref: "#/components/schemas/Team" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2325,6 +2337,8 @@ paths: $ref: "#/components/responses/401" "403": $ref: "#/components/responses/403" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2377,6 +2391,8 @@ paths: $ref: "#/components/responses/401" "403": $ref: "#/components/responses/403" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2414,6 +2430,8 @@ paths: $ref: "#/components/responses/401" "400": $ref: "#/components/responses/400" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" post: @@ -2445,6 +2463,8 @@ paths: $ref: "#/components/responses/401" "400": $ref: "#/components/responses/400" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "503": @@ -2521,6 +2541,8 @@ paths: $ref: "#/components/responses/401" "400": $ref: "#/components/responses/400" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2560,6 +2582,8 @@ paths: $ref: "#/components/responses/401" "400": $ref: "#/components/responses/400" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2605,6 +2629,8 @@ paths: $ref: "#/components/responses/404" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2666,6 +2692,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2695,6 +2723,8 @@ paths: $ref: "#/components/responses/404" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2719,6 +2749,8 @@ paths: $ref: "#/components/responses/404" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2767,6 +2799,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -2801,6 +2835,8 @@ paths: $ref: "#/components/responses/404" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "503": @@ -2843,6 +2879,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "503": @@ -2895,6 +2933,8 @@ paths: $ref: "#/components/responses/404" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "503": @@ -2942,6 +2982,8 @@ paths: $ref: "#/components/responses/404" "409": $ref: "#/components/responses/409" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "503": @@ -2976,6 +3018,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3009,6 +3053,8 @@ paths: $ref: "#/components/responses/404" "409": $ref: "#/components/responses/409" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3039,6 +3085,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" /sandboxes/{sandboxID}/snapshots: post: @@ -3074,6 +3122,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3119,6 +3169,8 @@ paths: $ref: "#/components/schemas/SnapshotInfo" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3157,6 +3209,8 @@ paths: $ref: "#/components/responses/403" "409": $ref: "#/components/responses/409" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3200,6 +3254,8 @@ paths: $ref: "#/components/responses/401" "403": $ref: "#/components/responses/403" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3238,6 +3294,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3273,6 +3331,8 @@ paths: $ref: "#/components/schemas/Template" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3305,6 +3365,8 @@ paths: $ref: "#/components/schemas/TemplateWithBuilds" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" delete: @@ -3326,6 +3388,8 @@ paths: description: The template was deleted successfully "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" patch: @@ -3356,6 +3420,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3388,6 +3454,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3423,6 +3491,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3474,6 +3544,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3533,6 +3605,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3568,6 +3642,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" delete: @@ -3597,6 +3673,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3630,6 +3708,8 @@ paths: $ref: "#/components/responses/403" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3666,6 +3746,8 @@ paths: $ref: "#/components/responses/403" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3696,6 +3778,8 @@ paths: $ref: "#/components/schemas/Node" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3727,6 +3811,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" post: @@ -3752,6 +3838,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3782,6 +3870,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3804,6 +3894,8 @@ paths: $ref: "#/components/schemas/AdminTeamRunningSandboxCounts" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3834,6 +3926,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3874,6 +3968,8 @@ paths: $ref: "#/components/responses/403" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3903,6 +3999,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3929,6 +4027,8 @@ paths: $ref: "#/components/schemas/TeamAPIKey" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" post: @@ -3953,6 +4053,8 @@ paths: $ref: "#/components/schemas/CreatedTeamAPIKey" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -3983,6 +4085,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" delete: @@ -4005,6 +4109,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -4032,6 +4138,8 @@ paths: $ref: "#/components/schemas/Volume" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -4064,6 +4172,8 @@ paths: $ref: "#/components/responses/400" "401": $ref: "#/components/responses/401" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -4093,6 +4203,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -4117,6 +4229,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" @@ -4361,6 +4475,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "501": @@ -4397,6 +4513,8 @@ paths: $ref: "#/components/responses/404" "409": $ref: "#/components/responses/409" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "501": @@ -4439,6 +4557,8 @@ paths: $ref: "#/components/responses/404" "409": $ref: "#/components/responses/409" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "501": @@ -4472,6 +4592,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "501": @@ -4515,6 +4637,8 @@ paths: $ref: "#/components/responses/401" "404": $ref: "#/components/responses/404" + "429": + $ref: "#/components/responses/429" "500": $ref: "#/components/responses/500" "501": diff --git a/spec/runtime-ref b/spec/runtime-ref index d508db9524..832eb88a75 100644 --- a/spec/runtime-ref +++ b/spec/runtime-ref @@ -1 +1 @@ -eee804e37a7a0e61b1be1e4dc8d89359f124798c +756512ca8bfdd5947bf9b21619581419eebaf69c From 5085b814e0d4f14d204125423e028c7bf3a0bded Mon Sep 17 00:00:00 2001 From: Michal Suba Date: Tue, 15 Sep 2026 11:45:07 +0200 Subject: [PATCH 6/6] chore: merge the two upload-headers changesets into one Co-Authored-By: Claude Fable 5 --- .changeset/azure-template-upload-headers-python.md | 5 ----- .changeset/azure-template-upload-headers.md | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .changeset/azure-template-upload-headers-python.md diff --git a/.changeset/azure-template-upload-headers-python.md b/.changeset/azure-template-upload-headers-python.md deleted file mode 100644 index fbbefca86e..0000000000 --- a/.changeset/azure-template-upload-headers-python.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@e2b/python-sdk": patch ---- - -Apply the request headers the API returns with a template layer-file upload link. Azure Blob Storage requires `x-ms-blob-type` on the upload request, which its signed URL cannot carry, so `COPY` instructions failed on Azure-backed clusters. GCS- and S3-backed clusters return no headers and are unaffected. diff --git a/.changeset/azure-template-upload-headers.md b/.changeset/azure-template-upload-headers.md index ddf3f9417a..2295a0b468 100644 --- a/.changeset/azure-template-upload-headers.md +++ b/.changeset/azure-template-upload-headers.md @@ -1,5 +1,6 @@ --- "e2b": patch +"@e2b/python-sdk": patch --- Apply the request headers the API returns with a template layer-file upload link. Azure Blob Storage requires `x-ms-blob-type` on the upload request, which its signed URL cannot carry, so `COPY` instructions failed on Azure-backed clusters. GCS- and S3-backed clusters return no headers and are unaffected.