Skip to content

Latest commit

 

History

History
151 lines (127 loc) · 9.31 KB

File metadata and controls

151 lines (127 loc) · 9.31 KB

Parity: @upstash/box (JS) ↔ upstash-box (Python)

The Python SDK mirrors the TypeScript SDK at packages/sdk. This file is the checklist consulted on every JS change and is enforced by scripts/check_parity.py (symbol-name level).

Naming: JS camelCase → Python snake_case. JS Box/EphemeralBox → Python sync Box/EphemeralBox (canonical) + async AsyncBox/AsyncEphemeralBox. JS Run/StreamRun → Python Run/StreamRun (+ AsyncRun/AsyncStreamRun).

Module exports

JS Python
Box / EphemeralBox Box / EphemeralBox (+ Async*)
Run / StreamRun Run / StreamRun (+ Async*)
BoxError BoxError
inferDefaultProvider infer_default_provider
runCustomHarness run_custom_harness
Agent, ClaudeCode, OpenAICodex, OpenCodeModel, OpenRouterModel, VercelModel, CursorModel, BoxApiKey same names (str-Enums)

Box instance methods/properties

JS Python
agent.run / agent.stream agent.run / agent.stream
exec.command / code / stream / streamCode exec.command / code / stream / stream_code
files.read/write/list/upload/download files.read/write/list/upload/download
files.stat/mkdir/rename/remove files.stat/mkdir/rename/remove
exec.session (live WebSocket session) exec.session
git.clone/diff/status/commit/updateConfig/push/createPR/exec/checkout git.clone/diff/status/commit/update_config/push/create_pr/exec/checkout
schedule.exec/agent/list/get/update/pause/resume/delete same (snake)
skills.add/remove/list skills.add/remove/list
labels.add/remove/list labels.add/remove/list
cd, cwd cd, cwd
configureModel, configureCustomHarness, modelConfig configure_model, configure_custom_harness, model_config
updateNetworkPolicy, networkPolicy update_network_policy, network_policy
getStatus, pause, resume, delete get_status, pause, resume, delete
snapshot, listSnapshots, deleteSnapshot snapshot, list_snapshots, delete_snapshot
getInitCommand, setInitCommand, deleteInitCommand get_init_command, set_init_command, delete_init_command
logs, listRuns logs, list_runs
getPublicURL, listPublicURLs, deletePublicURL get_public_url, list_public_urls, delete_public_url
id, size, keepAlive id, size, keep_alive
browser.tab.create browser.tab.create
browser.listTabs / browser.getTab / browser.cdpUrl browser.list_tabs / browser.get_tab / browser.cdp_url
browser.recordings.start/stop/list/get same (snake)
browser.recordings.download(id, { path? }) browser.recordings.download(id, path=...)

Tab (browser)

JS Python
goto, content, observe, act, close same
screenshot({type, fullPage}) screenshot(encoding=, full_page=) — see drift table
extract(instruction, schema, options?) extract(instruction, schema, *, model=None)
liveViewUrl live_view_url
id, url, title id, url, title

Recording handles (BrowserRecordingHandle.stop()) carry the same stale-handle guard as JS: stop checks its own recording first and never fires the box-wide stop for an already-ended recording. expires_at is normalized from the API's epoch seconds to ms in both SDKs.

Box static methods

JS Python
create, get, getByName, list, fromSnapshot create, get, get_by_name, list, from_snapshot
delete (bulk) delete_boxes (renamed to avoid clashing with instance delete)
deleteSnapshots delete_snapshots
setEnv, listEnv, deleteEnv, setAllEnv set_env, list_env, delete_env, set_all_env

EphemeralBox

Exposes only exec, files, schedule, cd, cwd, network_policy, get_status, delete, snapshot, list_snapshots, delete_snapshot, and statics create, from_snapshot, get_by_name, delete_boxes, delete_snapshots, expires_at. No agent, git, or skills. get_by_name returns a Box (mirrors the JS quirk), not an EphemeralBox.

Intentional exceptions (tracked drift)

Symbol / behavior Reason
inferDefaultRunner (JS) Deprecated alias of inferDefaultProvider — not ported.
getPreviewUrl / listPreviews / deletePreview (JS) Deprecated → use get_public_url / list_public_urls / delete_public_url.
modelConfig.provider / .runner (JS) Deprecated aliases dropped; Python model_config returns {harness, model} only.
agent config provider / runner (JS) Deprecated; Python accepts harness only.
Preview type (JS) Legacy alias of PublicURL; Python exports only PublicURL.
response_schema = Pydantic model or raw dict (Python) Narrower than JS Zod by design (v1).
close / aclose / context managers (Python) httpx transport lifecycle — JS has no equivalent (fetch-per-call).
delete_boxes (Python) JS static delete renamed.
appendTelemetryIdentity (JS) Internal hook for JS wrappers (the CLI) to join the telemetry chain; no Python wrapper exists, so not ported.
enableTelemetry config option (JS) Needed because edge runtimes (Cloudflare Workers) have no process.env to carry the opt-out; Python always has os.environ, so UPSTASH_DISABLE_TELEMETRY suffices.
timeouts in milliseconds Matches the JS SDK units.
agent options keys are snake_case (Python) vs camelCase (JS) Pythonic public API; the SDK converts to the backend's per-harness casing (Claude Code / OpenCode → camelCase, Codex → snake_case).
StreamRun.aclose() needed for detached on early break Python doesn't run generator finally on break (JS for await does).
Browser schema = Pydantic model or raw dict (Python) vs Zod (JS) Same ResponseSchema contract as agent.run; raw dicts skip client-side validation.
screenshot type: "png"|"base64" (JS) → encoding: "bytes"|"base64" (Python) Python returns native bytes; encoding matches files.read naming.
browser on from_snapshot (Python) Python's shared create-body builder forwards browser=True on from_snapshot; JS fromSnapshot currently omits it (JS gap).

Behavioral quirks mirrored exactly

  • Polling loops (2s/300s) in create, from_snapshot, snapshot.
  • is_agent_configured: bool(data.agent) on create; bool(data.model) on get/from_snapshot.
  • Run.logs(): epoch→ISO, lower-bound-only time filter.
  • Run.cancel(): swallows endpoint errors, always sets cancelled.
  • files.download destination: ./{basename} | ./workspace | ./{basename(cwd)}.
  • 3-mode run request: file paths → multipart, base64 objects → JSON files, else plain JSON.

Test mapping (JS unit file → Python)

infer-runnertest_infer_provider; box-createtest_box_create; box-agent-run + runtest_box_agent_run + test_run; box-exec-streamtest_box_exec; box-filestest_box_files; box-gittest_box_git; box-scheduletest_box_schedule; box-skills/box-config-model/box-preview/lifecycle/logs → test_box_misc; box-snapshot/box-from-snapshot/box-delete-snapshotstest_box_snapshot + test_box_statics; box-list/box-delete/box-envtest_box_statics; ephemeral-box(+from-snapshot) → test_ephemeral_box; custom-harnesstest_custom_harness; errortest_errors; box-browsertest_box_browser; helpers → covered by tests/helpers.py; box-instancetest_box_instance; models → test_models; helpers/common → test_common. Sync coverage: tests/_sync/test_sync_client + test_sse_golden.

exec.session

exec.session() is a live command session (stdin, PTY, signals, streaming) over a WebSocket, so it is the one feature not carried by httpx. It adds a websockets dependency, imported lazily so it only loads when a session is opened.

Both handles are hand-written in upstash_box/_exec_session.py rather than generated: the async handle pumps frames with an asyncio task and the sync handle with a reader thread, an asymmetry scripts/generate_sync.py cannot produce by token substitution. Frame construction, signal validation, and decoding are shared between them so the wire protocol has one definition. generate_sync.py maps AsyncExecSessionHandle/open_async_exec_session to the sync pair by name.

Naming follows the SDK's snake_case convention, so the handle is end_stdin/exec_id where JS is endStdin/execId, and callbacks are on_stdout/on_stderr taking bytes. The sync wait() additionally accepts a timeout, since blocking forever on a thread has no async equivalent to cancellation.

Note that scripts/check_parity.py does not gate any of this. Its extractor walks one level deep — it sees Box.exec and Box.files, not exec.session or files.stat — so nested namespace methods are outside the gate. Treat this file as the source of truth for namespace-level parity until the extractor recurses.