Skip to main content
Rolling red sand dunes at dusk
New releases, breaking changes, and notable improvements across the Reactor platform: JavaScript SDK, Python SDK, models, and API.
reactor-sdk 1.5.0 (Python)

New

  • Wheels for Alpine Linux. pip install reactor-sdk now finds a wheel on a musl 1.2+ distribution, x86_64 and aarch64. Before, there was none, and pip resolved to an older release with a different API. Pin reactor-sdk>=1.0 to get an error instead.

Fixed

  • A FileRef nested inside send_command()’s data now reaches the model. Put one in a list or a dict, and the SDK sends it as the upload reference the model reads. Before, the call raised TypeError, so a parameter that takes several files needed a hand-written {"upload_id": ...} dict, which you can now delete. A top-level FileRef still travels beside the arguments.
js-sdk 3.0.2

Fixed

  • A FileRef nested inside sendCommand()’s data now reaches the model. Pass one inside an array or object, and the SDK sends it as the upload reference the model reads. Before, it crossed the wire with the class’s own field names (uploadId, mimeType), and the model refused it. A parameter that takes several files needed a hand-written { upload_id } object, which you can now delete. A top-level FileRef still travels beside the arguments.
C++ SDK 2.0.1
Rolls up the C++ SDK’s 2.0.0 and 2.0.1 releases.

Breaking

  • Audio tracks use on_frame() and push_frame(), as video already did. on_audio() and push_audio() are gone. Take an AudioFrame in the handler you pass to on_frame(), and push interleaved 16-bit PCM with push_frame(Samples{...}). The overload has to match the track’s kind(), so a video handler on an audio track throws InvalidStateError. Rename the two calls. Nothing else about them changed.
  • pause() and resume() throw on a sendonly track. Both control what the client receives, and a sendonly audio track now refuses them exactly as a video one already did. Code that paused a track it publishes did nothing before, and throws now.
  • Options::local skips the API-key exchange and selects localhost. With api_url left at its default, a local connection goes to reactor::LOCAL_API_URL (http://localhost:8080) rather than to production. A key you passed is no longer exchanged for a token. A custom api_url is kept as it is, and so is a JWT you supplied. This matches the Python SDK.

New

  • fetch_jwt(). Exchanges an API key for a token without building a client or opening a session, for a backend that mints tokens somebody else will use. FetchJwtOptions scopes the token to a set of models, caps how many sessions it may open and how long each one runs, and sets its lifetime. It returns std::future<std::string>, and a failure arrives as the same typed ReactorError a call throws.
  • A constructor that takes no credential. reactor::Reactor{model, options} with options.local = true connects to a local runtime, which needs none.
  • on_capabilities_received() and on_session_id_changed(). The model’s capabilities as they arrive, and the session id whenever it is set or cleared (std::nullopt). Register both before connect() to catch the first of each. The declared tracks are refreshed before capabilities are delivered, so tracks() is already current inside that handler.
  • A FileRef can go inside a command’s arguments. A parameter that takes several files has no named slot in the uploads map. Pass those references in args instead: a FileRef converts to Json as the upload reference the model reads, and Json::array({first, second}) is that list. A parameter that takes one file is still named in uploads.
C++ SDK 1.0.0 · reactor-sdk 1.3.0 → 1.4.0 (Python)
Rolls up the C++ SDK’s first release and reactor-sdk 1.3.0 and 1.4.0.

New

  • The Reactor C++ SDK’s first release. A C++17 client for native applications (engines, capture pipelines, desktop clients), built on the same Rust core the Python and JavaScript SDKs bind, and shipped as one prebuilt archive per platform rather than through a package manager. The base Reactor class and Track cover connecting, commands, uploads, recordings, events, connection-wide and per-track bitrate control, and connection stats — calls return std::future<T>, frames arrive as BGRA on the library’s delivery thread. Speaker and Microphone (reactor::sdk_audio) play a recvonly audio track through real speakers or capture a real microphone into a sendonly one. See Installation and Using the SDK.
  • get_stats(). A snapshot of the live connection, as a ConnectionStats: RTT, jitter, packet loss, bitrates, and the WebRTC engine’s own per-stream counters. An unmeasured field reads None rather than zero. The SDK derives the two measured bitrates against the previous call, so they stay None until a second call at least 200 ms later. Only callable once the session is ready.
  • Reactor.set_bitrate() and Track.set_bitrate(). Bound the connection’s whole budget, and one sender’s share of it, in bits per second. The two apply together and the lower wins. For video, the track-level ceiling is the one to raise: with nothing set, WebRTC caps anything above 960x540 at 2.5 Mbps, 4K included.
  • max_session_duration_seconds on the Reactor() constructor. Terminates every session the client’s own minted token creates after that many seconds, whatever the token’s expiry says. The same constraint fetch_jwt() takes, now available without minting the token yourself. Ignored when you pass jwt.
  • RecorderDisabledError. request_clip() / request_recording() raise this instead of a bare ReactorError when the model’s recorder is disabled or has crashed. Retrying the call will not help. The recorder has to be restarted on the model side. See Error codes.
  • get_stats() reports the transport the session uses. candidate_type is "host", "srflx", "prflx", or "relay", and "relay" means the media goes through a TURN server. That is the first thing to check when latency is bad. relay_protocol names that server’s own transport ("udp", "tcp", or "tls").
  • Three fields the JavaScript SDK already reported are here too. available_incoming_bitrate_bps and available_outgoing_bitrate_bps carry the congestion controller’s estimate of what the path can carry, not what flows over it. frames_per_second is the rate on the received video stream.
  • The per-stream report carries more. InboundStream and OutboundStream now name their kind ("audio" or "video") and count video frames. A send stream also carries what the far end reports back about it: total_round_trip_time_s, fraction_lost, and packets_lost. CandidatePair gains nominated, writable, its own byte and packet totals, and the two bitrate estimates.

Changed

  • close() settles operations still in flight. A send_command() whose reply had not arrived when close() ran now raises AbortedError in the coroutine that awaits it. Before, that await stayed pending for the life of the process. disconnect() was never affected.
  • push_frame() refuses raw bytes that do not match their dimensions. A BGRA buffer that is not exactly width * height * 4 bytes raises ValueError. Only the dimensions cross to the native side, never the length, so a mismatch used to send the reader past the end of the buffer. Frames pushed as a numpy array are unaffected.
  • Prebuilt wheels. pip install reactor-sdk now installs a wheel with the native library already in it, one per platform: Linux on glibc 2.34 or newer (x86_64, aarch64), macOS 11+ on arm64 and 13+ on x86_64, and Windows x86_64. One wheel covers every supported interpreter, 3.10 and up. There is no source distribution, so anywhere else pip quietly resolves to an older release. Pin reactor-sdk>=1.0 to get an error instead.
  • jitter_s and packet_loss_ratio now come from the received video stream. That is the stream the JavaScript SDK reads, so the two SDKs report the same number for the same connection. They used to aggregate across every receive stream. An audio-only session still gets both: they fall back to the receive streams there are, where the JavaScript SDK reports nothing.
  • The two measured bitrates now cover the candidate pair that carries the media. incoming_bitrate_bps and outgoing_bitrate_bps count everything that pair carried, RTCP and the data channel included. Before, they summed RTP payload and read a little low. One consequence: both read None until ICE selects a pair, on top of the cases that already read None.
  • rtt_ms comes from that same pair, rather than from the highest-priority succeeded one.

Fixed

  • An outbound stream’s round_trip_time_s was always 0.0. It now carries the measured value.
  • A send stream’s packets_sent and retransmitted_packets_sent wrapped after about 4.3 billion packets, which is roughly seven weeks at a thousand packets a second. After the wrap, a cumulative counter appeared to go backwards. Both are 64-bit now, as are the candidate pair’s.
js-sdk 3.0.1

New

  • setTrackBitrate() raises the ceiling on a track you publish. With no bound set, a Chromium-based browser caps a sender above 960x540 at 2500 kbps, so a 720p or 1080p publish never exceeds 2.5 Mbps even on a fast connection. Call it with maxBps after publishTrack() to give the encoder more room. minBps sets a floor, which only Chromium honors. There is no connection-wide equivalent, because browsers do not expose one.
js-sdk 3.0.0
A major version bump for @reactor-team/js-sdk. This release moves session and protocol handling into the same underlying implementation the Python and C++ SDKs share — the SDK’s own JavaScript/TypeScript no longer drives the browser’s WebRTC APIs by hand; that layer does, over the same wire protocol.

Breaking

  • sendCommand() now waits for its reply, and resolves with it. The returned promise used to resolve as soon as the command was sent, before the model replied. It now genuinely awaits the model’s correlated reply, bounded by a timeout, and resolves with the reply payload (or undefined if the handler acknowledged the command without sending one back). A call site that awaits or otherwise consumes the returned promise now waits for the real round trip instead of resolving almost immediately; a caller that never awaited it in the first place is unaffected.
  • ReactorError is a typed class hierarchy, not a flat object. It’s now the base of 16 concrete subclasses (UnauthorizedError, ConflictError, RateLimitedError, and more — see Error codes), each carrying a stable code, recoverable, status, operation, retry_after_ms, and timestamp_ms. The old component field ("api"/"gpu") is gone. retryAfter/timestamp are kept alongside the new field names for compatibility.

New

  • Track introspection on the base class. tracks(), trackMapping(), pausedTracks(), and getTrackByName()/getStreamByName() / getTrackByMid()/getStreamByMid() resolve a named or negotiated track to its MediaStreamTrack/MediaStream directly off a Reactor instance.
  • pauseTrack() / resumeTrack() are asynchronous. Both now return a Promise<void>, queued behind the same control round-trip as other track operations, instead of applying synchronously.
  • getSchema() / schemaReceived. The model’s command schema, fetched automatically on the first "ready" transition and cached for synchronous reads.
  • once(). Registers an event listener that fires at most once, then removes itself.
  • isFileRef(). A structural check for anything shaped like a FileRef, for callers who’d rather duck-type than rely on instanceof.
  • useReactor() exposes more store state. lastMessage, jwtToken, and connectOptions are now readable off the store, and pauseTrack/resumeTrack are store actions alongside publish/unpublish.

Removed

  • getState(). Read getStatus(), getLastError(), and getSessionId() individually instead.
  • The sessionExpirationChanged event. It never carried a real value in any deployment — dropped rather than carried forward.
reactor-sdk 1.0.0 (Python)

Breaking

  • send_command() now waits for its reply. Previously fire-and-forget — the call resolved as soon as the command was queued, and any reply from the model arrived later as a separate message event. It now returns the model’s correlated reply directly: {"type": ..., "data": ...}, or None if the handler acknowledged the command without returning a message (an auto-generated set_<field> setter, for instance). To fire a command without waiting on the reply, schedule the call instead of awaiting it directly — asyncio.create_task(reactor.send_command(...)) — keeping a reference to the task so it isn’t garbage-collected before it completes.
  • Errors are now typed exceptions, and ReactorError is the one class for both. Every failure used to arrive as one bare exception carrying only a sentence, and separately, on_error handed you a same-shaped-but-different dataclass. ReactorError is now both: the base of 16 concrete subclasses (UnauthorizedError, ConflictError, RateLimitedError, and more — see Error codes) that on_error and a raised call hand you identically, each with a stable code, recoverable, status, operation, retry_after_ms, and (event-only) timestamp_ms. The old component field (api/gpu) is gone — it split codes by platform tier, which wasn’t actionable and could report the same failure under two different codes. except ReactorError still catches everything; code relying on error.component needs updating.
  • Audio devices are synthetic-only. The constructor’s adm_mode argument is gone; no Reactor instance ever opens a real microphone or speaker on its own. A sendonly audio track only ever carries PCM you push yourself with Track.push_frame(), and a model’s audio must be played back explicitly via Track.on_frame() / on_raw_frame() — or with Speaker/Microphone, below.
  • reactor.on_frame (client-wide) is removed. It only ever worked for video, and a single handler fed every recvonly video track couldn’t tell them apart. Register on the Track instead, found by name (reactor.track(name)) or by filtering reactor.tracks (reactor.tracks.with_direction("recvonly").with_kind("video").one()).
  • The client-wide on("frame", ...) / on("audio", ...) events are removed too, for the same reason. on() raises ValueError at registration for either name, naming the Track method to use instead — on_frame() for decoded frames, on_raw_frame() for the same bytes with no NumPy conversion.
  • publish_track() / unpublish_track() are the only name-based track methods still public on Reactor. push_video_frame(), push_audio_frame(), pause_track(), and resume_track() are now internal — reach the same operations through the Track object instead: track.push_frame(), track.pause(), track.resume(). publish_track() already hands back that same Track.
  • Track.push_frame() now raises InvalidStateError on an unpublished sendonly track, rather than silently dropping the frame. Call publish() first — Track.published says whether that’s already happened.
  • reconnect() resumes recvonly tracks automatically, but does not restore a sendonly track’s publish. Call publish() again after reconnecting for anything you were sending before it.
  • @reactor.on_track hands over the Track itself, not a bare (name, mid). The Track is already resolved — reactor.track(name) done for you — so a handler that wants to push frames or register on_frame no longer has to look it up first; track.mid still carries the WebRTC media stream ID.
  • unpublish_track() / Track.unpublish() return None, not an int. A failure is now logged (reactor_sdk at WARNING) rather than left for the caller to notice by checking a return value nobody checked — unpublish is commonly the last call in a finally block, so it still does not raise.
  • Reactor()’s positional arguments are model_name, api_keyapi_url moved to keyword-only. Matches the old py-sdk’s own constructor order exactly, so Reactor(model, key) ported from there needs no rewrite to keyword arguments at all. Previously api_url/model_name were positional (in either order, sniffed by which one looked like a URL); any call relying on that specifically needs updating, but a plain Reactor("your-model-name") is unaffected.
  • upload_file() now requires ready. Previously allowed as soon as the session was created, before the WebRTC handshake finished — raises InvalidStateError now if called earlier, the same guard request_clip() and Track.pause() already used. See File Uploads.

New

  • reactor.tracks is now a TrackList. Still a plain list[Track] for iterating and indexing, with with_kind() / with_direction() filters that chain, and .one() for the common case of “the track I mean, and an error if that’s ambiguous.” See TrackList.
  • The Track object. reactor.track(name) and reactor.tracks return a Track scoped to one named slot, with publish(), push_frame(), on_frame()/on_raw_frame(), and pause()/resume() that raise on a direction mismatch instead of a silent no-op.
  • Track.published. Whether a sendonly track is currently activated — readable state for something the session itself doesn’t track, cleared automatically whenever the connection leaves ready.
  • download_clip(). Fetches every segment a Clip’s playlist_url names. Given a path, streams straight to it (returns None, never holds more than one segment in memory — the one to use for request_recording()); without one, returns the assembled bytes instead. Takes an optional on_progress callback either way. Closes a real gap left by the rewrite off the old py-sdk, which had this as download_clip_as_file().
  • reactor.download_clip(seconds) / reactor.download_recording(). request_clip() / request_recording() plus the download above, in one call, for when the file is all you want. request_clip() / request_recording() still return just the Clip for anyone who also wants its session_id, markers, or predicted_ready_at_ms.
  • connect(connection_id=...). Adopts a WebRTC connection slot a backend already registered for a session, the connection-level analogue of session_id adoption — see Multiple connections per session. Also closes a gap left by the rewrite: the old py-sdk had this, and the JavaScript SDK still does.
  • reconnect() now works from ready, not only after a drop. It tears the live connection down itself first — without ending the session, unlike disconnect() — so there was never a need to call disconnect() immediately before it; that combination is also no longer required. disconnect()’s own behavior is unchanged — it has always ended the session server-side, with no recoverable option — a few places in these docs previously described it backwards, now corrected. See Sessions.
  • Speaker / Microphone (reactor_sdk.audio_devices). Play a recvonly audio track through real speakers, or capture a real microphone into a sendonly track — context managers wrapping a sounddevice stream around a Track: with Speaker(output_track), Microphone(mic_track): .... Not re-exported from reactor_sdk — the only part of this SDK with a runtime dependency, so pip install "reactor-sdk[audio]" and import from reactor_sdk.audio_devices directly. Speaker promotes what examples/pygame_app had hand-rolled; Microphone is new.
  • fetch_jwt(api_key, api_url=...)’s api_url is optional now, defaulting to the same production API Reactor() itself defaults to. Pass it explicitly only when minting a token against a different one.
helios · Rewind

New

  • Rewind. Helios saves snapshots of the scene and returns to them on demand. save_snapshot captures the world state, rewind restores it, and list_snapshots reports what the buffer holds. The buffer keeps up to 50 snapshots per session and evicts the oldest once full; a reset empties it, and it is released when the session ends. Each snapshot records the one it descends from, so a save after a rewind branches off the restored snapshot while a save without one extends the branch in progress, and current_snapshot_id on the state message says which branch is live. Rewinding restores the scene but not the prompt, so send the prompt you want to continue with after the rewind. Available in @reactor-models/helios as saveSnapshot, rewind, and listSnapshots, with the matching useHeliosSnapshotSaved, useHeliosRewindComplete, useHeliosRewindFailed, and useHeliosSnapshotList hooks.
js-sdk · Multi-client connections

Breaking

  • Publishing an input track is now explicit. Output tracks still stream automatically on connect, but sendonly (input) tracks send nothing until your app calls publishTrack() (or renders <WebcamStream>). Apps that relied on input media flowing at connect must now publish explicitly once the connection is ready. See Tracks.

New

  • Output track pause/resume. New pauseTrack() and resumeTrack() methods, plus an autoResumeTracks connect option (default true), control when each output track streams.
  • Multi-client sessions. A single session can now back several connections at once. connect() accepts sessionId and connectionId options to attach to a session or connection your backend created.
Pricing

Changed

  • Pricing shown per second, in dollars. Model pricing on the Pricing & Billing page now leads with a per-second US-dollar rate, matching how sessions are billed, with a per-hour figure shown for reference.
Docs · Pricing & billing

New

  • Pricing & billing docs. Published Pricing & Billing, covering session pricing, idle vs. active billing, and recoverable-state behavior.
  • Beta status callouts. Added a banner across docs and the Overview noting that Reactor is in beta. APIs and pricing may evolve.
Authentication · API

New

  • Token TTL with expires_after. Auth tokens now carry an expires_after field so clients can refresh proactively instead of waiting for a 401. See Authentication.
js-sdk · reactor-sdk · Concepts

New

  • File uploads in the Python SDK. reactor-sdk now supports upload_file() returning a FileRef that can be passed into send_command().
  • File uploads in the JavaScript SDK. uploadFile() and FileRef are available in @reactor-team/js-sdk for passing files (images, audio, blobs) as command arguments.
  • New concept page: File Uploads documents the upload-then-reference pattern in both SDKs.
API

Breaking

  • Token endpoint is now POST. The auth-token endpoint moved from GET to POST to allow request-body parameters. Update any direct API callers; the SDKs handle this automatically once upgraded.
js-sdk 2.7 · reactor-sdk 0.4

Breaking

  • New SDK protocol. Wire-level protocol overhaul shipped in @reactor-team/js-sdk 2.7 and reactor-sdk 0.4. Older SDK versions will no longer connect. Upgrade to the latest release.
  • Removed fetchInsecureToken() and JWT helpers. The development-only insecure token helpers were removed; all auth now flows through the standard token endpoint.

Improved

  • Maple theme refresh. Docs site updated to Mintlify’s Maple theme with Lucide icons, new codeblock styling, and refreshed colors.
  • Navigation split into separate “SDK” and “API Reference” tabs.
Helios

New

  • Image conditioning for Helios. set_image() accepts a reference image to steer generation, with cut or blend transitions for swapping mid-stream. See Helios.

Improved

  • Image size guidance clarified: 64KB limit with JPEG quality 0.5 recommended for set-image calls.
js-sdk 2.6.0

Breaking

  • API renames in @reactor-team/js-sdk 2.6.0. Several public methods and types were renamed for consistency with the Python SDK. Update import paths and call sites.
Helios

New

  • Helios launch. Reactor’s first interactive real-time video generation model, built on a 14B-parameter Diffusion Transformer. Produces a continuous infinite stream you can steer with prompts and reference images.
  • Helios prompt guide. A dedicated Prompt Guide covering scene composition, transitions, and steering techniques.
Docs

Improved

  • Renamed “Realtime AI” → “Realtime Video AI” across the docs to better reflect what Reactor does. No API or SDK changes.
reactor-sdk (Python)

New

  • Python SDK launched. reactor-sdk is now available on PyPI, async-first, with parity for sessions, commands, and message handling. See Using the SDK.
js-sdk

Breaking

  • sendCommand() replaces sendMessage(). The command API was renamed to clarify the distinction between commands (client → model) and messages (model → client). Update all sendMessage call sites. See Commands and messages.
  • Removed message queueing. Commands now have explicit waiting state instead of implicit queueing. Applications can decide whether to drop, replace, or buffer in-flight commands.
Authentication · API

Breaking

  • JWT auth replaces insecureApikey. All client connections now require a short-lived JWT minted server-side from your API key. The insecureApikey development flow is removed.
  • New API Reference section. Published a complete API Reference covering the Reactor class, React components, hooks, types, and events.
Docs

New

  • Reactor docs site launched. Initial release with Overview and Quickstart. Ported from the legacy readme.io site.