generateWorld() builds a world on the server from a prompt. The result is one of two kinds:
Warning
World generation is experimental. The API can change. Live world models bill for each second that a session holds a GPU. World Labs bills in credits for each generation.
This streams a live Reactor Orbis world. It takes about 10 minutes.
npm i @tanstack/ai @tanstack/ai-reactor @reactor-team/js-sdkpnpm add @tanstack/ai @tanstack/ai-reactor @reactor-team/js-sdkyarn add @tanstack/ai @tanstack/ai-reactor @reactor-team/js-sdkbun add @tanstack/ai @tanstack/ai-reactor @reactor-team/js-sdkREACTOR_API_KEY=your-reactor-keyimport { generateWorld } from '@tanstack/ai'
import { reactorWorld } from '@tanstack/ai-reactor'
export async function POST(request: Request) {
const body: unknown = await request.json()
const prompt =
typeof body === 'object' &&
body !== null &&
'prompt' in body &&
typeof body.prompt === 'string'
? body.prompt
: ''
if (prompt.length === 0) {
return Response.json({ error: 'prompt is required' }, { status: 400 })
}
const world = await generateWorld({
adapter: reactorWorld('visko-orbis-stable'),
prompt,
threadId: 'thread-1',
})
return Response.json({
token: world.token,
model: world.model,
prompt: world.prompt,
expiresAt: world.expiresAt,
})
}import { Reactor } from '@reactor-team/js-sdk'
const video = document.querySelector('video')
if (!video) {
throw new Error('Missing video element')
}
const world = await fetch('/api/world', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt:
'A dramatic coastline of black volcanic cliffs at golden hour, a single unbroken take.',
}),
}).then(async (response) => {
if (!response.ok) {
throw new Error('World session failed')
}
return response.json()
})
const token = typeof world.token === 'string' ? world.token : ''
const model = typeof world.model === 'string' ? world.model : ''
const prompt = typeof world.prompt === 'string' ? world.prompt : ''
if (token.length === 0 || model.length === 0 || prompt.length === 0) {
throw new Error('World payload is incomplete')
}
const reactor = new Reactor({ modelName: model })
reactor.on('trackReceived', (name, _track, stream) => {
if (name !== 'main_video') return
video.muted = true
const attach = () => {
video.srcObject = null
video.srcObject = stream
void video.play().catch((error: unknown) => {
if (error instanceof DOMException && error.name === 'AbortError') return
})
}
attach()
for (const track of stream.getTracks()) {
track.addEventListener('unmute', attach)
}
})
await reactor.connect(token)
await reactor.sendCommand('set_resolution', { resolution: '1080p' })
await reactor.sendCommand('set_prompt', { prompt })
await reactor.sendCommand('start', {})The video element now plays a live world. To steer it, send a new set_prompt. The scene changes at the next chunk.
The work splits in two:
There is no client hook for worlds. For the shared model of other media types, see Generations.
reactorWorld() takes these ids. The model field on the result is the Reactor connect slug.
| Id | Connect slug | What it does |
|---|---|---|
| visko-orbis-stable | reactor/visko-orbis-stable | Steerable video with live audio |
| visko-orbis-dynamic | reactor/visko-orbis-dynamic | Same family, can change resolution while it runs |
| lingbot-world-2 | reactor/lingbot-world-2 | A world you can move through, anchored on an image |
| lingbot | reactor/lingbot | Video you can move through, anchored on an image |
| helios | reactor/helios | Interactive live video |
More notes:
World Labs Marble runs a job, not a live stream. generateWorld() waits until the world is ready. Then it returns a viewer URL and asset links. Set WORLDLABS_API_KEY on the server.
import { generateWorld } from '@tanstack/ai'
import { worldlabsWorld } from '@tanstack/ai-worldlabs'
const world = await generateWorld({
adapter: worldlabsWorld('marble-1.1'),
prompt: 'A mystical forest with glowing mushrooms',
threadId: 'thread-1',
})
console.log(world.url) // The Marble viewerWhat you get back:
Models: marble-1.1-plus, marble-1.1 (the World Labs default), marble-1.0, and marble-1.0-draft. See the World Labs adapter for image and video inputs.
A full app is in examples/ts-react-media. Open the World tab. Pick a Reactor model for a live stream, or a Marble model for a finished 3D world. What the example shows:
LingBot and LingBot World 2 start from an image. Pass a File from <input type="file" accept="image/png,image/jpeg">. The SDK uploads the file. Do not send base64.
start also needs set_prompt. Write the prompt so it describes what the image shows. If the prompt and the image disagree, the image wins and the world drifts.
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 })
await reactor.sendCommand('start', {})The browser can save a Reactor key and send it in an x-byok-reactor header. The server reads that header first, then REACTOR_API_KEY. Do not put the key in the JSON body.
Server:
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 dramatic coastline of black volcanic cliffs at golden hour',
threadId: 'thread-1',
})
return Response.json({
token: world.token,
model: world.model,
prompt: world.prompt,
})
}Browser:
import { defineByok, defaultByokStorage } from '@tanstack/ai-client/byok'
import { reactorByok } from '@tanstack/ai-reactor/byok'
const byok = defineByok({
storage: defaultByokStorage(),
providers: [reactorByok],
})
byok.setServerCoverage(true)
const world = await fetch('/api/world', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...byok.headers(reactorByok.id),
},
}).then((response) => response.json())See Bring Your Own Key for the key store and a save UI.
Pass modelOptions: { wait: false } to generateWorld(). Then it returns at once with an operationId. This SDK cannot resume that id. See the World Labs adapter.