You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
💥 Add sandbox API client and split query params from request bodies - #21
Adds a generated sandboxapi client from the Blaxel sandbox execution-plane spec, plus a thin SandboxClient/AsyncSandboxClient wrapper exported for direct execution-plane access; the hand-written baseten.sandbox surface is the higher-level face.
Updates the management spec from staging, adding the /v1/sandboxes CRUD and image paths plus /v1/token (prod spec does not serve these yet).
💥 Breaking: query parameters now go on params; request bodies stay on request. Mirrors the JS change in baseten-js#22. Bumped to 0.12.0.
💻 How
Generator now emits success-code collections, non-JSON request bodies (multipart via httpx files, octet-stream via content), and a _raw sibling returning the unread httpx.Response for operations with non-JSON success content types (with an accept argument when the operation is content-negotiated).
Query and body may coexist only in the sandbox API; management and inference still enforce query-XOR-body and the generator raises if a spec breaks that.
New tests/client/test_sandbox.py (13 cases): bearer token and opt-out, multipart upload with query+body, octet-stream body, raw sibling accept, status-narrowed export (ExportResult vs ExportProgress), typed and untyped errors, async variants.
poe lint, poe typecheck, poe test (62 passed) green; generation is idempotent so the CI regen-diff holds.
Note on diff size: the "files changed" stat is inflated by GitHub's diff renderer on the two large repetitive files (management.json, managementapi/_models.py) — it deletes and re-adds identical lines. The actual diff is +19.3k/−1.4k; git diff main...HEAD --shortstat shows it.
Staging spec (api.staging.baseten.co/v1/spec) adds the /v1/sandboxes
surface over prod. Prod spec still serves 160 paths without sandboxes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Query parameters now go on `params` and request bodies on `request`
(both may coexist on sandbox operations; management and inference still
enforce query-XOR-body). Success responses are a code collection,
non-JSON bodies (multipart, octet-stream) are supported, and operations
with non-JSON success responses get a `_raw` sibling that takes an
`accept` and returns the httpx response unread.
Generator changes: rename injected query schemas XRequest -> XParams,
hoist inline 2xx response schemas into components, fold non-identifier
schema names (archive.Change -> ArchiveChange) into PascalCase, accept
YAML specs, and add the sandbox spec from blaxel-ai/sandbox.
Management spec updated from staging, which adds /v1/sandboxes and
/v1/token over prod. New internal SandboxClient/AsyncSandboxClient
wrapper. Bumped to 0.12.0 for the breaking argument split.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Raw streaming responses are eagerly buffered, aliased request fields are unusable or serialized incorrectly, and mixed bodyless success statuses are mishandled.
Query parameters referenced via components/parameters, and parameters
inherited from the path item, were dropped silently. The sandbox
management operations take their team selector (and cursor/limit on
listings) that way, so they generated without any way to pass a team.
The query-XOR-body invariant is now path-scoped: operations under
/v1/sandboxes may carry query parameters alongside a request body, the
rest of the management and inference APIs may not.
Co-Authored-By: Claude Code <noreply@anthropic.com>
…dispatch
- Generated classes with aliased fields get populate_by_name=True, and
request models serialize by_alias, so a field renamed for Python
(async_ for "async") is passable by its Python name and reaches the
API under its own.
- Raw methods stream: build_request + send(stream=True) via a new
_do_raw; error paths read the body first, and the docstring says the
caller closes the response.
- An operation with several success codes and at least one JSON schema
always dispatches by status; bodyless codes (e.g. 204 beside a JSON
200) yield None.
Co-Authored-By: Claude Code <noreply@anthropic.com>
JSON selection drops binary and multipart request encodings
scripts/apigen/clientgen.py:197
When a request body declares JSON plus other encodings, this unconditionally selects JSON and drops the others. The inference PredictInput declares application/json, application/octet-stream, and multipart/form-data, so every generated predict* method only accepts request: PredictInput; callers still cannot send binary or multipart inference inputs. Generate encoding-specific siblings (or an explicit content-type/body API) instead of discarding declared encodings.
This same signature is emitted for async methods, but a regular IO[bytes] becomes a synchronous HTTPX request stream, which AsyncClient.send() rejects. The async post_process_stdin annotation therefore promises an input that fails at runtime. Emit bytes | str | AsyncIterable[bytes] for async clients (or restrict them to buffered values), while retaining IO[bytes] only for sync clients.
Typed JSON sibling omits Accept for raw response operations
scripts/apigen/clientgen.py:558
The typed JSON sibling leaves Accept unset even when the operation also has raw response media types. HTTPX then sends its default Accept: */*; for example, /filesystem/{path} explicitly uses Accept to select JSON versus octet-stream, so get_filesystem() can receive binary data and fail in _do_json. Set Accept: application/json for the non-raw sibling whenever op.raw_accepts is non-empty.
The thin per-sandbox wrapper is the advanced-user entry to the generated
execution-plane client, mirroring the JS SDK's @basetenlabs/client export.
The higher-level surface stays in baseten.sandbox.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The staging pull had swapped every api.baseten.co URL in servers and
examples for the staging host; the snapshot is hand-curated from staging
until prod serves the sandbox paths, but its URLs should stay prod. The
spec download URL reverts to prod to match the JS SDK, and the README
keeps its shape as before this PR.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The generated client already handled both uniformly; the gate was only a
build-time spec lint, and it needed a /v1/sandboxes carve-out to accept
the current spec, so it guarded nothing. Server-side conventions like
query-XOR-body belong to the server's spec tests, not the SDK build.
Co-Authored-By: Claude Code <noreply@anthropic.com>
yaml.safe_load yields datetime.date for date-only scalars, which the
unbound datetime.isoformat default rejects; serialize with the value's
own isoformat. Raw-sibling docstrings appended a sentence after summaries
that already end in a period, producing a doubled period.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Default sync client times out idle streaming responses
baseten/client/_sandbox.py:75
This default HTTPX client retains the 5-second read timeout, but the generated log and filesystem-watch methods return long-lived streams that may be idle for longer than five seconds. Out of the box, iterating either stream can therefore raise ReadTimeout during normal inactivity. Configure a stream-suitable read timeout or expose timeout configuration without requiring callers to replace the whole HTTP client.
Default async client times out idle streaming responses
baseten/client/_sandbox.py:162
The async default client also keeps HTTPX's 5-second read timeout, so an idle get_process_logs_stream or get_watch_filesystem iterator fails with ReadTimeout even though these endpoints are intended to remain open. Configure a stream-suitable read timeout or expose timeout configuration without requiring a full client override.
JSON sibling lacks Accept header for raw response alternatives
scripts/apigen/clientgen.py:557
The typed JSON sibling leaves Accept unset even when the operation can also return a raw representation. HTTPX then sends Accept: */*, so the server may legitimately choose application/octet-stream, after which _do_json raises despite a successful response. Set Accept: application/json for the JSON sibling whenever raw alternatives exist.
Query-plus-body support violates management API invariant
scripts/apigen/preprocess.py:235
Removing this guard enables query-plus-body operations for every generated API, not only sandbox. The updated management spec already generates create_sandbox, update_sandbox, and push_image with both params and request, contradicting the PR's stated management query-XOR-body invariant. Either enforce this per API (with an explicit sandbox exception) or update the stated contract and cover management coexistence as intentional.
The reason will be displayed to describe this comment to others. Learn more.
Matching the JS generator deliberately: it also prefers JSON when an operation declares several body encodings (the same choose-JSON rule in his clientgen), and the extra media types on PredictInput were equally unreachable before this PR. A content selector for multi-media bodies is a design question tracked with Chad alongside the accept/response-type design he offered to help with.
AsyncClient cannot send a sync file object, so the async octet-stream
body signature accepts bytes and str, while the sync one keeps IO[bytes].
Co-Authored-By: Claude Code <noreply@anthropic.com>
Forward-patches the spec snapshot with GET /v0/sandbox/hub and the
SandboxHubImage/SandboxHubImageList schemas, shaped from the live
Blaxel spec, and regenerates the typed client.
Co-Authored-By: Claude Code <noreply@anthropic.com>
SandboxHubImage -> SandboxLibraryImage and ListSandboxLibraryImages in
the spec snapshot, matching the library_models naming of the consumer
catalog. Type and operation names are client-side only; the wire path
stays /v0/sandbox/hub.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Replaces the Blaxel hub forward-patch with GET /v1/sandboxes/library
_images from the served management spec (baseten#31334), matching
baseten-go 7b8c438. The server drops hidden and coming-soon entries.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Matches what baseten#31334 will serve, per baseten-go 7b8c438: state
and display_name drop from the sandbox record and create/update
requests, create_if_not_exists arrives on create.
Co-Authored-By: Claude Code <noreply@anthropic.com>
cretz
changed the title
Add sandbox API client and split query params from request bodies
💥 Add sandbox API client and split query params from request bodies
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🚀 What
sandboxapiclient from the Blaxel sandbox execution-plane spec, plus a thinSandboxClient/AsyncSandboxClientwrapper exported for direct execution-plane access; the hand-writtenbaseten.sandboxsurface is the higher-level face./v1/sandboxesCRUD and image paths plus/v1/token(prod spec does not serve these yet).params; request bodies stay onrequest. Mirrors the JS change in baseten-js#22. Bumped to 0.12.0.💻 How
files, octet-stream viacontent), and a_rawsibling returning the unreadhttpx.Responsefor operations with non-JSON success content types (with anacceptargument when the operation is content-negotiated).XRequest->XParams, inline 2xx response schemas hoisted into components, dotted schema names folded to PascalCase (archive.Change->ArchiveChange), YAML specs supported.🔬 Testing
tests/client/test_sandbox.py(13 cases): bearer token and opt-out, multipart upload with query+body, octet-stream body, raw sibling accept, status-narrowed export (ExportResultvsExportProgress), typed and untyped errors, async variants.poe lint,poe typecheck,poe test(62 passed) green; generation is idempotent so the CI regen-diff holds.🤖 Generated with Claude Code
Note on diff size: the "files changed" stat is inflated by GitHub's diff renderer on the two large repetitive files (
management.json,managementapi/_models.py) — it deletes and re-adds identical lines. The actual diff is +19.3k/−1.4k;git diff main...HEAD --shortstatshows it.