TanStack
Adapters

Reactor

Reactor hosts live world and video models. You describe a scene. Then you open a session and stream video. You can steer the stream with a new prompt.

Use reactorWorld() with generateWorld() for navigable worlds. Use reactorVideo() with generateLiveVideo() for live video models. Both mint a session token. Neither supports chat().

Installation

shell
npm i @tanstack/ai-reactor

Peer dependency: @tanstack/ai. The browser also needs @reactor-team/js-sdk to connect and play the stream. See World Generation and Live Generation.

A full working app is in examples/ts-react-media. Open the World or Live tab.

API key

Create a key in the Reactor dashboard. Keys start with rk_.

The example app uses Bring Your Own Key. The browser pastes the key. The relay reads x-byok-reactor, then REACTOR_API_KEY.

ts
import { generateWorld } from '@tanstack/ai'
import { byokMissing, getByokKey } from '@tanstack/ai/byok/server'
import { reactorWorld } from '@tanstack/ai-reactor'
import { reactorByok } from '@tanstack/ai-reactor/byok'

export async function POST(request: Request) {
  const apiKey = getByokKey(request, reactorByok)
  if (!apiKey) return byokMissing(reactorByok)

  const world = await generateWorld({
    adapter: reactorWorld('visko-orbis-stable', { apiKey }),
    prompt: 'A neon cyberpunk city at night, slow aerial drift',
  })

  return Response.json({
    token: world.token,
    model: world.model,
    prompt: world.prompt,
    expiresAt: world.expiresAt,
  })
}

The adapter mints a session-scoped token for that model only. Hand world.token, world.model, and world.prompt to the browser. Do not put the API key in the JSON body.

Models

Pass a string literal so TypeScript can narrow options.

ts
import { reactorWorld } from '@tanstack/ai-reactor'

const adapter = reactorWorld('visko-orbis-stable')
IdConnect slug
visko-orbis-stablereactor/visko-orbis-stable
visko-orbis-dynamicreactor/visko-orbis-dynamic
happy-oyster-adventurereactor/happy-oyster-adventure
happy-oyster-directorreactor/happy-oyster-director
lingbot-world-2reactor/lingbot-world-2
lingbotreactor/lingbot
heliosreactor/helios

world.model is the connect slug. Pass it to new Reactor({ modelName }).

Live video

Reactor video is a live stream, not a finished file. generateLiveVideo() returns a token. The browser connects, sets the prompt, and plays the track.

ts
import { generateLiveVideo } from '@tanstack/ai'
import { reactorVideo } from '@tanstack/ai-reactor'

const apiKey = process.env.REACTOR_API_KEY ?? ''
const live = await generateLiveVideo({
  adapter: reactorVideo('helios', { apiKey }),
  prompt: 'A neon cyberpunk city at night, slow aerial drift',
})

Hand live.token, live.model, and live.prompt to the browser. Connect as shown in Live Generation.

IdConnect slug
heliosreactor/helios
fast-h3reactor/fast-h3
longlive-v2reactor/longlive-v2
ltx2reactor/ltx2

helios also works with reactorWorld(). Pick generateLiveVideo() when you want a video session. Pick generateWorld() when you want a navigable world.

Pass a text prompt to generateWorld() / generateLiveVideo(). Those calls mint a token. They do not send resolution or a seed image.

After connect, send browser commands. LingBot starts from a seed image. Pass a File from <input type="file">. The SDK uploads it and returns a FileRef. Do not send base64. start still needs set_prompt. Send a short default, then steer after the first frame.

ts
import { Reactor } from '@reactor-team/js-sdk'

const reactor = new Reactor({ modelName: 'reactor/lingbot' })
const picker = document.querySelector('input[type="file"]')
if (!(picker instanceof HTMLInputElement)) {
  throw new Error('Pick a seed image')
}
const file = picker.files?.[0]
if (file === undefined) {
  throw new Error('Pick a seed image')
}
const image = await reactor.uploadFile(file)
await reactor.sendCommand('set_image', { image })
await reactor.sendCommand('set_prompt', { prompt: 'Follow the seed image.' })
await reactor.sendCommand('start', {})

Helios can take the same File with set_conditioning so prompt and image land together.

Browser session options

Orbis reads these on the next start. Keep them in client state. Send them with sendCommand after connect. They are not token-mint fields.

ts
import { Reactor } from '@reactor-team/js-sdk'

const reactor = new Reactor({ modelName: 'reactor/visko-orbis-stable' })
await reactor.sendCommand('set_resolution', { resolution: '2k' })
await reactor.sendCommand('set_seed', { seed: 42 })
await reactor.sendCommand('set_audio_enabled', { enabled: true })
OptionCommandMeaning
resolutionset_resolution1080p, 2k, or 4k delivery tier
seedset_seedRNG seed for the next run
audioEnabledset_audio_enabledWhen false, skip audio compute
audioPromptset_audio_promptSound description, or "" for picture-driven audio

Custom endpoint

ts
import { reactorWorld } from '@tanstack/ai-reactor'

const apiKey = process.env.REACTOR_API_KEY ?? ''
const adapter = reactorWorld('visko-orbis-stable', {
  apiKey,
  baseUrl: 'https://api.reactor.inc',
})

What you have now

A server call that mints a scoped Reactor token for one world or video model. Next: connect in the browser as shown in World Generation or Live Generation.