@reactor-models/x2
v0.5.2
Published
Strongly-typed SDK for the X2 model on Reactor
Readme
@reactor-models/x2
Typed JavaScript + React SDK for the X2 model on Reactor. Version v0.5.2.
Get started
Scaffold a starter app for X2 with create-reactor-app:
npx create-reactor-app my-app --model=x2pnpm dlx create-reactor-app my-app --model=x2Install
npm install @reactor-models/x2pnpm add @reactor-models/x2The package exports a plain-JavaScript client and a set of React bindings. Import whichever you need from @reactor-models/x2:
import { X2Model } from "@reactor-models/x2";import { X2Provider, useX2 } from "@reactor-models/x2";React 18 or later is required when using the provider and hooks. The token-loading examples below use React 19's use(); on React 18, fetch the JWT in a useEffect and pass it to the provider once it resolves.
Authenticate
Reactor uses short-lived JWTs for session auth. You hold your API key on your server, mint a token on demand, and the client never sees the raw key. Tokens are valid for 6 hours — if one leaks, it expires on its own.
Mint a JWT with POST https://api.reactor.inc/tokens and the Reactor-API-Key header; the response JSON is { "jwt": "..." }.
JavaScript (Next.js route handler)
// app/api/reactor/token/route.ts
import { NextResponse } from "next/server";
export async function POST() {
const res = await fetch("https://api.reactor.inc/tokens", {
method: "POST",
headers: { "Reactor-API-Key": process.env.REACTOR_API_KEY! },
});
const { jwt } = await res.json();
return NextResponse.json({ jwt });
}React (provider)
Call the /api/reactor/token route above from a client component and pass the result to the provider:
"use client";
import { use } from "react";
import { X2Provider } from "@reactor-models/x2";
import { ReactorView } from "@reactor-team/js-sdk";
async function getToken() {
const r = await fetch("/api/reactor/token", { method: "POST" });
const { jwt } = await r.json();
return jwt;
}
const tokenPromise = getToken();
export default function App() {
const token = use(tokenPromise);
return (
<X2Provider jwtToken={token} connectOptions={{ autoConnect: true }}>
<ReactorView className="w-full aspect-video" />
</X2Provider>
);
}Connect
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);React
The provider takes the JWT as a prop; fetch it from the same /api/reactor/token route the Authenticate example mints:
"use client";
import { use } from "react";
import { X2Provider, useX2 } from "@reactor-models/x2";
async function getToken() {
const r = await fetch("/api/reactor/token", { method: "POST" });
const { jwt } = await r.json();
return jwt;
}
const tokenPromise = getToken();
function Controller() {
const { status } = useX2();
return <span>Status: {status}</span>;
}
export default function App() {
const token = use(tokenPromise);
return (
<X2Provider jwtToken={token}>
<Controller />
</X2Provider>
);
}Events
Client-to-model commands. The typed surface is X2Model (one method per event) in plain JS, and useX2() in React — every field name below matches the parameter name the method accepts.
reset
Stop generation and clear the prompt, reference image, and pointer, returning the session to waiting for new conditions. Valid any time. Emits generation_stopped with reason reset if a run was active, and state_update.
Emits: generation_stopped, state_update
No parameters.
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.reset();React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { reset } = useX2();
return <button onClick={() => reset()}>reset</button>;
}setPrompt
Set the editing instruction that guides the re-render (for example a character swap or insertion). A non-empty prompt is required before generation begins; setting it while generating applies from the next block. Emits prompt_accepted, then state_update.
Emits: prompt_accepted, state_update
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | | The editing instruction, e.g. "replace the person with the character from the reference image". Up to 1000 characters. Applies from the next generated block. (maxLength 1000, default "") |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setPrompt({ prompt: "A sunset over the ocean" });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setPrompt } = useX2();
return <button onClick={() => setPrompt({ prompt: "A sunset over the ocean" })}>setPrompt</button>;
}setPointer
Update the drag pointer that steers the edited subject's motion, setting its position and press state together. Valid any time; the pointer is sampled once per generated block and only has an effect while active. Emits pointer_changed, then state_update.
Emits: pointer_changed, state_update
| Parameter | Type | Required | Description |
|---|---|---|---|
| x | number | | Horizontal pointer position, normalized to the output frame (0 = left, 1 = right). (min 0, max 1, default 0.5) |
| y | number | | Vertical pointer position, normalized to the output frame (0 = top, 1 = bottom). (min 0, max 1, default 0.5) |
| active | boolean | | Whether a drag gesture is in progress. While true, the pointer steers motion; while false, it is ignored. (default false) |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setPointer({ x: 0.5, y: 0.5, active: false });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setPointer } = useX2();
return <button onClick={() => setPointer({ x: 0.5, y: 0.5, active: false })}>setPointer</button>;
}setPointerX
Set pointer_x
| Parameter | Type | Required | Description |
|---|---|---|---|
| pointer_x | number | | Horizontal drag-pointer position, normalized to the output frame (0 = left edge, 1 = right edge). Sampled once per generated block and only steers motion while pointer_active is true. (min 0, max 1, default 0.5) |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setPointerX({ pointer_x: 0.5 });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setPointerX } = useX2();
return <button onClick={() => setPointerX({ pointer_x: 0.5 })}>setPointerX</button>;
}setPointerY
Set pointer_y
| Parameter | Type | Required | Description |
|---|---|---|---|
| pointer_y | number | | Vertical drag-pointer position, normalized to the output frame (0 = top edge, 1 = bottom edge). Sampled once per generated block and only steers motion while pointer_active is true. (min 0, max 1, default 0.5) |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setPointerY({ pointer_y: 0.5 });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setPointerY } = useX2();
return <button onClick={() => setPointerY({ pointer_y: 0.5 })}>setPointerY</button>;
}setKeepBacklog
Set keep_backlog
| Parameter | Type | Required | Description |
|---|---|---|---|
| keep_backlog | boolean | | Source-frame consumption policy, applied from the next block. False (default): always read the newest frames and drop backlog, keeping latency bounded when inference is slower than the source. True: consume every frame in order for smoother motion (e.g. drag mode) at the cost of growing delay. (default false) |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setKeepBacklog({ keep_backlog: false });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setKeepBacklog } = useX2();
return <button onClick={() => setKeepBacklog({ keep_backlog: false })}>setKeepBacklog</button>;
}setPointerActive
Set pointer_active
| Parameter | Type | Required | Description |
|---|---|---|---|
| pointer_active | boolean | | Whether a drag gesture is in progress. While true, the current pointer_x/pointer_y position steers the motion of the edited subject; while false, the pointer is ignored. Sampled once per generated block. (default false) |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
await x2.setPointerActive({ pointer_active: false });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setPointerActive } = useX2();
return <button onClick={() => setPointerActive({ pointer_active: false })}>setPointerActive</button>;
}setReferenceImage
Provide a reference image of a character or object to insert or swap into the video. Can be set before generation or replaced while generating; replacing it mid-run restarts the stream (generation_stopped with reason reference_image_changed, then a fresh generation_started) so the new reference conditions it from the first block. Emits reference_image_accepted and state_update on success, or command_error if the file cannot be decoded as an image.
Emits: generation_stopped, generation_started, reference_image_accepted, state_update, command_error
| Parameter | Type | Required | Description |
|---|---|---|---|
| reference_image | FileRef | ✅ | Reference to a file uploaded via the Reactor presigned-URL protocol. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
const fileRef = await x2.uploadFile(blob);
await x2.setReferenceImage({ reference_image: fileRef });React
"use client";
import { useX2 } from "@reactor-models/x2";
function Example() {
const { setReferenceImage, uploadFile } = useX2();
async function handlePick(file: File) {
const ref = await uploadFile(file);
await setReferenceImage({ reference_image: ref });
}
return <input type="file" onChange={(e) => handlePick(e.target.files![0])} />;
}Messages
Model-to-client messages. Register a typed listener with on… on X2Model, or a useX2… hook in React, to receive only the messages you care about.
state_update
Emitted on connect and after every observable state change; carries the full state in one payload.
Listener: onStateUpdate · React hook: useX2StateUpdate
| Field | Type | Description |
|---|---|---|
| width | unknown | Output frame width in pixels once a run has started, or null before the resolution is chosen. Fixed for the whole session. |
| height | unknown | Output frame height in pixels once a run has started, or null before the resolution is chosen. Fixed for the whole session. |
| prompt | unknown | The active editing instruction, or null when none is set. Generation does not run without a prompt. |
| pointer_x | number | Current horizontal drag-pointer position, normalized to the output frame (0 = left, 1 = right). |
| pointer_y | number | Current vertical drag-pointer position, normalized to the output frame (0 = top, 1 = bottom). |
| generating | boolean | Whether a generation run is currently producing main_video frames. |
| keep_backlog | boolean | Current source-frame policy. False: read the newest frames and drop backlog (bounded latency). True: consume every frame in order (smoother motion, growing delay). |
| pointer_active | boolean | Whether a drag gesture is currently steering the edited subject. |
| has_reference_image | boolean | Whether a reference image is currently conditioning generation. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onStateUpdate((msg) => {
console.log(
"state_update",
msg.width,
msg.height,
msg.prompt,
msg.pointer_x,
msg.pointer_y,
msg.generating,
msg.keep_backlog,
msg.pointer_active,
msg.has_reference_image,
);
});
await x2.connect(jwt);React
import { useX2StateUpdate } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2StateUpdate((msg) => {
console.log(
"state_update",
msg.width,
msg.height,
msg.prompt,
msg.pointer_x,
msg.pointer_y,
msg.generating,
msg.keep_backlog,
msg.pointer_active,
msg.has_reference_image,
);
});command_error
Emitted when a command cannot be applied; the command has no effect.
Listener: onCommandError · React hook: useX2CommandError
| Field | Type | Description |
|---|---|---|
| reason | string | Human-readable explanation of why the command was rejected. |
| command | string | Name of the command that failed, e.g. set_reference_image. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onCommandError((msg) => {
console.log("command_error", msg.reason, msg.command);
});
await x2.connect(jwt);React
import { useX2CommandError } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2CommandError((msg) => {
console.log("command_error", msg.reason, msg.command);
});pointer_changed
Emitted after set_pointer updates the drag pointer.
Listener: onPointerChanged · React hook: useX2PointerChanged
| Field | Type | Description |
|---|---|---|
| x | number | Horizontal pointer position, normalized to the output frame (0 = left, 1 = right). |
| y | number | Vertical pointer position, normalized to the output frame (0 = top, 1 = bottom). |
| active | boolean | Whether a drag gesture is in progress. While true, the pointer steers the edited subject's motion. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onPointerChanged((msg) => {
console.log(
"pointer_changed",
msg.x,
msg.y,
msg.active,
);
});
await x2.connect(jwt);React
import { useX2PointerChanged } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2PointerChanged((msg) => {
console.log(
"pointer_changed",
msg.x,
msg.y,
msg.active,
);
});prompt_accepted
Emitted after set_prompt succeeds; echoes the accepted prompt.
Listener: onPromptAccepted · React hook: useX2PromptAccepted
| Field | Type | Description |
|---|---|---|
| prompt | string | The editing instruction now in effect. It conditions generation from the next block onward. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onPromptAccepted((msg) => {
console.log("prompt_accepted", msg.prompt);
});
await x2.connect(jwt);React
import { useX2PromptAccepted } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2PromptAccepted((msg) => {
console.log("prompt_accepted", msg.prompt);
});generation_started
Emitted once when a generation run begins producing main_video.
Listener: onGenerationStarted · React hook: useX2GenerationStarted
| Field | Type | Description |
|---|---|---|
| width | number | Output frame width in pixels. Chosen once at the first generation and fixed for the whole session. |
| height | number | Output frame height in pixels. Chosen once at the first generation and fixed for the whole session. |
| prompt | string | The editing instruction the run started with. |
| has_reference_image | boolean | Whether a reference image is conditioning this run. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onGenerationStarted((msg) => {
console.log(
"generation_started",
msg.width,
msg.height,
msg.prompt,
msg.has_reference_image,
);
});
await x2.connect(jwt);React
import { useX2GenerationStarted } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2GenerationStarted((msg) => {
console.log(
"generation_started",
msg.width,
msg.height,
msg.prompt,
msg.has_reference_image,
);
});generation_stopped
Emitted when a generation run ends; no more main_video frames follow until it restarts.
Listener: onGenerationStopped · React hook: useX2GenerationStopped
| Field | Type | Description |
|---|---|---|
| reason | string | Why generation stopped: reset (cleared by the reset command) or reference_image_changed (a new reference image triggered an automatic restart, and a fresh generation_started follows). |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onGenerationStopped((msg) => {
console.log("generation_stopped", msg.reason);
});
await x2.connect(jwt);React
import { useX2GenerationStopped } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2GenerationStopped((msg) => {
console.log("generation_stopped", msg.reason);
});reference_image_accepted
Emitted after set_reference_image decodes an uploaded image.
Listener: onReferenceImageAccepted · React hook: useX2ReferenceImageAccepted
| Field | Type | Description |
|---|---|---|
| width | number | Width, in pixels, of the decoded reference image, before it is fitted to the output resolution. |
| height | number | Height, in pixels, of the decoded reference image, before it is fitted to the output resolution. |
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onReferenceImageAccepted((msg) => {
console.log("reference_image_accepted", msg.width, msg.height);
});
await x2.connect(jwt);React
import { useX2ReferenceImageAccepted } from "@reactor-models/x2";
// Inside a React component wrapped by <X2Provider>:
useX2ReferenceImageAccepted((msg) => {
console.log("reference_image_accepted", msg.width, msg.height);
});Tracks
Named media channels between your app and the X2 model. Use the typed helpers below — X2Model.publish<Track> / on<Track> in plain JS, and useX2Track or the per-track <X2<Track>View> components in React — so track names are checked at compile time.
source
A video channel you push media into (for example, the user's webcam).
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
await x2.connect(jwt);
const media = await navigator.mediaDevices.getUserMedia({ video: true });
const videoTrack = media.getVideoTracks()[0];
await x2.publishSource(videoTrack);
// later, to stop sending:
await x2.unpublishSource();React
"use client";
import { X2SourceView } from "@reactor-models/x2";
// Inside a component wrapped by <X2Provider>:
export function Example() {
return <X2SourceView />;
}main_video
A video channel you subscribe to — the model publishes this for your app to render.
JavaScript
import { X2Model } from "@reactor-models/x2";
const x2 = new X2Model();
x2.onMainVideo((track, stream) => {
// attach to a <video> element, pipe to a canvas, etc.
videoEl.srcObject = stream;
});
await x2.connect(jwt);React
"use client";
import { X2MainVideoView } from "@reactor-models/x2";
// Inside a component wrapped by <X2Provider>:
export function Example() {
return <X2MainVideoView className="w-full aspect-video" />;
}