> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reactor.inc/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The catalog of every model — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# Visko Orbis Stable schema reference

> The complete Visko Orbis Stable track, command, and event schema, with an end-to-end example.

This page documents the complete Visko Orbis Stable wire surface: the media tracks it produces, the
session lifecycle, every command you can send, and the messages the model emits back. For what Visko
Orbis Stable is and a quick start, see the
[overview](/model-api-reference/visko-orbis-stable/overview).

## Tracks

| Direction | Name         | Type  | Format                   | Rate          |
| --------- | ------------ | ----- | ------------------------ | ------------- |
| Outbound  | `main_video` | Video | `(N, H, W, 3)` uint8 RGB | 18 fps        |
| Outbound  | `main_audio` | Audio | 48 kHz mono              | chunk-aligned |

The model generates at 832 × 480 and delivers at the resolution you pick: `1080p` (1920×1080), `2k`
(2560×1440), or `4k` (3840×2160). Read the deployment's offered list of sizes from the `state`
snapshot's `available_resolutions`; never hard-code it. There are no inbound tracks; all
client-to-model communication is through commands.

## Session lifecycle

Once the connection reaches **ready**, the session begins in `WAITING`. `start` transitions to
`GENERATING` (provided a prompt is set); `pause` moves to `PAUSED`; `resume` returns to
`GENERATING`; `reset` clears state and returns to `WAITING` from any state. See
[Sessions](/concepts/sessions#connection-lifecycle) for the connection-level lifecycle
(`disconnected → connecting → waiting → ready`) the session passes through first.

When a run reaches its `max_chunks` limit (up to 229 chunks, \~7 minutes) the server emits
`generation_complete` and returns the session to `WAITING`. It does not roll into another run
because a new run begins at chunk 0, which would be a hard visual cut. Call `start` again with the
same conditions, or `reset` to clear the prompt and image first.

<Frame>
  <img src="https://mintcdn.com/reactortechnologiesinc/3wrpLd7R1K3eK0X3/diagrams/generation-states.svg?fit=max&auto=format&n=3wrpLd7R1K3eK0X3&q=85&s=94400371f5941145a8e6f870d6877577" alt="Session lifecycle: waiting → generating → paused, with generation_complete and reset returning to waiting" width="664" height="212" data-path="diagrams/generation-states.svg" />
</Frame>

## Commands

Send commands with `reactor.sendCommand()` on the base SDK, or the typed methods on
[`ViskoOrbisStableModel`](/sdk-reference/typed-model-sdk) / `useViskoOrbisStable()`. The setter
commands (`set_*`) take effect at the next chunk boundary.

**How commands report back.** On `@reactor-team/js-sdk` 3.x every command's success acknowledgement
is the **correlated reply** the awaited `sendCommand()` (or typed method) resolves with — delivered
to the calling connection only. Read the acknowledgement off the `await`, not off a
`reactor.on("message")` listener. The per-command examples below all follow that pattern. Reserve
`on("message")` for what the model genuinely **broadcasts** to every connected client — `state`,
`chunk_complete`, `generation_started` / `generation_paused` / `generation_resumed` /
`generation_complete` / `generation_reset`, `conditions_ready`, and `command_error`.

Below are all available commands:

| Command             | Description                                                           |
| ------------------- | --------------------------------------------------------------------- |
| `set_prompt`        | Set or hot-swap the scene prompt (valid before and during generation) |
| `set_audio_prompt`  | Set the sound description the audio is generated from                 |
| `set_image`         | Set a reference image that anchors generation (optional, I2V)         |
| `set_seed`          | RNG seed for the next run                                             |
| `set_resolution`    | Choose the delivery resolution from `available_resolutions`           |
| `set_audio_enabled` | Enable or disable sound for runs started from now on                  |
| `start`             | Begin generation (requires a prompt)                                  |
| `pause`             | Pause after the current chunk                                         |
| `resume`            | Resume from a pause                                                   |
| `reset`             | Clear all session state and return to `WAITING`                       |

### `set_prompt`

Set the scene prompt. Valid at any time — call before `start` to arm generation, or hot-swap during
generation to steer the next chunk. The picture **morphs** into the new prompt at the next chunk
boundary rather than cutting. This is the model's hero feature and what the tutorial's
[live steering](/model-api-reference/visko-orbis-stable/tutorial#hot-swapping-prompts-mid-stream)
section builds on. Replaces the previously active prompt; applied on the next chunk when generating,
otherwise when `start` fires. Returns `prompt_accepted` as the awaited reply; `conditions_ready` and
`state` are broadcast to every connected client.

**Parameters:**

| Parameter | Type   | Required | Description                               |
| --------- | ------ | -------- | ----------------------------------------- |
| `prompt`  | string | Yes      | Natural-language description of the scene |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const reply = await reactor.sendCommand("set_prompt", {
    prompt:
      "A dramatic coastline of black volcanic cliffs at golden hour, huge dark waves rolling in from a blood-orange sea. Cinematic slow aerial motion, a single unbroken take.",
  });
  // reply === { type: "prompt_accepted", data: { prompt: "A dramatic coastline…" } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { setPrompt } = useViskoOrbisStable();
  const reply = await setPrompt({
    prompt:
      "A dramatic coastline of black volcanic cliffs at golden hour, huge dark waves rolling in from a blood-orange sea. Cinematic slow aerial motion, a single unbroken take.",
  });
  // reply === { type: "prompt_accepted", data: { prompt: "A dramatic coastline…" } }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  reply = await reactor.send_command("set_prompt", {
      "prompt": "A dramatic coastline of black volcanic cliffs at golden hour, huge dark waves rolling in from a blood-orange sea. Cinematic slow aerial motion, a single unbroken take.",
  })
  # reply == {"type": "prompt_accepted", "data": {"prompt": "A dramatic coastline…"}}
  ```
</CodeGroup>

### `set_audio_prompt`

Set the sound description the audio is generated from. Valid at any time — call before `start`, or
during generation to change the sound from the next chunk on. Pass an empty string to clear it,
which switches the audio model to generating sound from the picture alone (the default, and measured
the best). Returns `audio_prompt_accepted` as the awaited reply on success; `state` is broadcast.
Rejected with `command_error` on a deployment that has no audio track.

**Parameters:**

| Parameter | Type   | Required       | Description                                                        |
| --------- | ------ | -------------- | ------------------------------------------------------------------ |
| `prompt`  | string | Yes (or empty) | The sound description, or empty to generate from the picture alone |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const reply = await reactor.sendCommand("set_audio_prompt", {
    prompt:
      "Acoustic guitar strums a rhythmic melody, with soft finger noise on the strings and quiet room ambience.",
  });
  // reply === { type: "audio_prompt_accepted", data: { audio_prompt: "Acoustic guitar…" } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { setAudioPrompt } = useViskoOrbisStable();
  const reply = await setAudioPrompt({ prompt: "" });
  // reply === { type: "audio_prompt_accepted", data: { audio_prompt: null } }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  reply = await reactor.send_command("set_audio_prompt", {"prompt": ""})
  # reply == {"type": "audio_prompt_accepted", "data": {"audio_prompt": None}}
  ```
</CodeGroup>

### `set_image`

Provide a starting frame the video grows out of (image-to-video). Optional — with no image the model
generates from the prompt alone. Call before `start`; the image anchors the first chunk and every
later chunk inherits it through the model's own history, so a change during generation has no effect
until `reset` and a new `start`. Upload the file first with `uploadFile()`, then pass the returned
[`FileRef`](/sdk-reference/types#fileref). Returns `image_accepted` as the awaited reply on success;
`conditions_ready` and `state` are broadcast, and the command is rejected with `command_error` if
the file is missing, not an image, or cannot be decoded.

**I2V ordering:** await `uploadFile`, then `setImage`, then `setPrompt`, then `start`, because a
`start` that races past an in-flight upload renders its first chunk unconditioned and visibly
flickers into the anchored composition a beat later. On `@reactor-team/js-sdk` 3.x `setImage`
resolves with the `image_accepted` reply once the image is decoded, so `await` is the whole
pattern. See the
[tutorial I2V flow](/model-api-reference/visko-orbis-stable/tutorial#starting-from-an-image).

<Warning>
  Non-16:9 starting images squash. The reference is resized to 832×480 **with no crop**. Use a 16:9
  frame.
</Warning>

**Parameters:**

| Parameter | Type    | Required | Description                                                  |
| --------- | ------- | -------- | ------------------------------------------------------------ |
| `image`   | FileRef | Yes      | A reference to an uploaded image, returned by `uploadFile()` |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const ref = await reactor.uploadFile(seedImageFile);
  const reply = await reactor.sendCommand("set_image", { image: ref });
  // reply === { type: "image_accepted", data: { width: 832, height: 480 } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { uploadFile, setImage } = useViskoOrbisStable();
  const ref = await uploadFile(seedImageFile);
  const reply = await setImage({ image: ref });
  // reply === { type: "image_accepted", data: { width: 832, height: 480 } }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  ref = await reactor.upload_file("seed.png")
  reply = await reactor.send_command("set_image", {"image": ref})
  # reply == {"type": "image_accepted", "data": {"width": 832, "height": 480}}
  ```
</CodeGroup>

### `set_seed`

RNG seed for the next run. Must be a non-negative integer; the model never draws its own seed, so
the same seed with the same prompts reproduces the same video. Read once when `start` fires; later
changes take effect only after `reset` followed by a new `start`.

**Parameters:**

| Parameter | Type | Required | Description                              |
| --------- | ---- | -------- | ---------------------------------------- |
| `seed`    | int  | No       | ≥ 0. Same seed → same output. Default 42 |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  await reactor.sendCommand("set_seed", { seed: 42 });
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { setSeed } = useViskoOrbisStable();
  await setSeed({ seed: 42 });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  await reactor.send_command("set_seed", {"seed": 42})
  ```
</CodeGroup>

### `set_resolution`

Choose the delivery resolution for `main_video` from this deployment's offered list
(`available_resolutions` in the `state` snapshot — e.g. `1080p`, `2k`, `4k`). These are delivery
tiers, not generation sizes: the model generates at 832×480 and the server delivers the picked
raster at that size. Session-scoped: read when `start` fires, so the track's geometry never jumps
mid-shot — call it before `start`, or any time to arm the next run. Resolution survives `reset`; the
prompt does not. Returns `resolution_accepted` as the awaited reply on success; `state` is
broadcast, and the command is rejected with `command_error` naming the offered list when the value
is not on it.

**Parameters:**

| Parameter    | Type   | Required | Description                                                                       |
| ------------ | ------ | -------- | --------------------------------------------------------------------------------- |
| `resolution` | string | Yes      | One of `state.available_resolutions` — named delivery tiers (`1080p`, `2k`, `4k`) |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const reply = await reactor.sendCommand("set_resolution", { resolution: "2k" });
  // reply === { type: "resolution_accepted", data: { width: 832, height: 480, resolution: "2k" } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { setResolution } = useViskoOrbisStable();
  const reply = await setResolution({ resolution: "2k" });
  // reply === { type: "resolution_accepted", data: { width: 832, height: 480, resolution: "2k" } }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  reply = await reactor.send_command("set_resolution", {"resolution": "2k"})
  # reply == {"type": "resolution_accepted", "data": {"width": 832, "height": 480, "resolution": "2k"}}
  ```
</CodeGroup>

### `set_audio_enabled`

Enable or disable sound for runs started from now on. When `false` the audio model is skipped
entirely — `main_audio` carries silence and each chunk is cheaper to produce. Session-scoped like
`set_resolution`: read when `start` fires, and it survives `reset`. Returns `audio_enabled_accepted`
as the awaited reply on success; `state` is broadcast. Rejected with `command_error` on a deployment
that has no audio track. A client that never wants audio can also simply omit `main_audio` from its
track mapping when connecting — that needs no command, but still spends the compute; this command
is how the compute is saved.

**Parameters:**

| Parameter       | Type | Required | Description                                                |
| --------------- | ---- | -------- | ---------------------------------------------------------- |
| `audio_enabled` | bool | Yes      | `true` to generate sound (the default), `false` to skip it |

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const reply = await reactor.sendCommand("set_audio_enabled", { audio_enabled: false });
  // reply === { type: "audio_enabled_accepted", data: { audio_enabled: false } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { setAudioEnabled } = useViskoOrbisStable();
  const reply = await setAudioEnabled({ audio_enabled: false });
  // reply === { type: "audio_enabled_accepted", data: { audio_enabled: false } }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  reply = await reactor.send_command("set_audio_enabled", {"audio_enabled": False})
  # reply == {"type": "audio_enabled_accepted", "data": {"audio_enabled": False}}
  ```
</CodeGroup>

### `start`

Begin generating video on `main_video`. Requires a prompt (via `set_prompt`); a starting image is
optional. Returns `generation_started` as the awaited reply on success; `state` is broadcast.
Rejected with `command_error` if no prompt is set. Has no effect while already generating.

### `pause`

Pause generation after the current chunk finishes. Frames stop streaming on `main_video` until
`resume` is called; the model keeps its place, so resuming continues the same shot rather than
starting a new one. Returns `generation_paused` as the awaited reply on success; `state` is
broadcast. Rejected with `command_error` if not generating or already paused.

### `resume`

Resume generation from a previous `pause`. Requires the session to be paused. Returns
`generation_resumed` as the awaited reply on success; `state` is broadcast. Rejected with
`command_error` if not paused.

### `reset`

Abort the current run, clear the active prompt and starting image, and return to the waiting state.
Valid at any time. After `reset`, call `set_prompt` (and optionally `set_image`) again before
`start`. Resolution and audio settings survive `reset`; prompt and image do not. Returns
`generation_reset` as the awaited reply; `state` is broadcast.

The lifecycle commands take no arguments. Each `await` resolves with that command's correlated
reply:

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const started = await reactor.sendCommand("start", {});
  // started === { type: "generation_started", data: { fps: 24, width: 832, height: 480, … } }
  const paused = await reactor.sendCommand("pause", {});
  // paused === { type: "generation_paused", data: { chunk_index: 3 } }
  const resumed = await reactor.sendCommand("resume", {});
  // resumed === { type: "generation_resumed", data: { chunk_index: 3 } }
  const reset = await reactor.sendCommand("reset", {});
  // reset === { type: "generation_reset", data: { reason: "client_reset" } }
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const { start, pause, resume, reset } = useViskoOrbisStable();
  // Each resolves with the correlated reply, e.g.:
  // const started = await start();  // → { type: "generation_started", data: {…} }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  started = await reactor.send_command("start", {})
  # started == {"type": "generation_started", "data": {"fps": 24, "width": 832, "height": 480, …}}
  paused = await reactor.send_command("pause", {})
  # paused == {"type": "generation_paused", "data": {"chunk_index": 3}}
  resumed = await reactor.send_command("resume", {})
  # resumed == {"type": "generation_resumed", "data": {"chunk_index": 3}}
  reset = await reactor.send_command("reset", {})
  # reset == {"type": "generation_reset", "data": {"reason": "client_reset"}}
  ```
</CodeGroup>

## Messages

Visko Orbis Stable emits the following messages. Every message is delivered as JSON
`{ "type": "<name>", "data": { … } }`. Each row's **Delivery** column says whether a connected
client sees the message as the correlated reply of a single awaited command (read it off the
`await`) or as a broadcast every connected client receives (subscribe with `on("message")`).

| Event                    | When                                                                            | Delivery      | Payload                                                                                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt_accepted`        | After `set_prompt`                                                              | Command reply | `{ prompt: string }`                                                                                                                                             |
| `audio_prompt_accepted`  | After `set_audio_prompt`                                                        | Command reply | `{ audio_prompt: string \| null }`                                                                                                                               |
| `image_accepted`         | After `set_image` decodes                                                       | Command reply | `{ width: int, height: int }`                                                                                                                                    |
| `resolution_accepted`    | After `set_resolution`                                                          | Command reply | `{ width: int, height: int, resolution: string }`                                                                                                                |
| `audio_enabled_accepted` | After `set_audio_enabled`                                                       | Command reply | `{ audio_enabled: bool }`                                                                                                                                        |
| `conditions_ready`       | After `set_prompt` or `set_image`                                               | Broadcast     | `{ has_image: bool, has_prompt: bool }`                                                                                                                          |
| `generation_started`     | After `start`                                                                   | Both          | `{ fps: number, width: number, height: number, max_chunks: number, resolution: string, audio_enabled: bool, frames_per_chunk: number, image_conditioned: bool }` |
| `generation_paused`      | After `pause` (chunk finishes)                                                  | Both          | `{ chunk_index: int }`                                                                                                                                           |
| `generation_resumed`     | After `resume`                                                                  | Both          | `{ chunk_index: int }`                                                                                                                                           |
| `generation_complete`    | After the final chunk of a run                                                  | Broadcast     | `{ total_chunks: int }`                                                                                                                                          |
| `generation_reset`       | After `reset`                                                                   | Both          | `{ reason: string }`                                                                                                                                             |
| `chunk_complete`         | After each chunk emits                                                          | Broadcast     | `{ chunk_index: int, active_prompt: string, frames_emitted: int, audio_samples: int \| null }`                                                                   |
| `command_error`          | When a command is rejected                                                      | Broadcast     | `{ command: string, reason: string }`                                                                                                                            |
| `state`                  | On connect, after every state-mutating command, and after each `chunk_complete` | Broadcast     | Full session snapshot (see below)                                                                                                                                |

<Note>
  **Both** means the message is the calling command's correlated reply *and* a broadcast every
  connected client receives — on the calling connection it surfaces twice: once as the value the
  awaited command resolves with, and once on the `message` event. Drive state off the `await` for
  the call that earned it; keep any `on("message")` listener for the broadcast side only (for
  example, a second connection that did not issue the command). `command_error` is the exception to
  that pattern: `sendCommand()` never rejects, so a rejection always surfaces on the `message` event
  — that's why it reads Broadcast, not Both. A rejected awaited call also lands on `getLastError()`.
</Note>

### `state` payload

`state` is the single source of truth for driving UI. Subscribe once and treat it as the
authoritative session snapshot; you generally do not need to track individual commands and
`chunk_complete` events yourself.

| Field                   | Type             | Meaning                                                                                                                                                        |
| ----------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `started`               | `bool`           | `start` succeeded. Stays true through pause. Cleared by `reset`. The phase switch.                                                                             |
| `running`               | `bool`           | `started and not paused`. Frames actively streaming.                                                                                                           |
| `paused`                | `bool`           | True while paused via `pause`.                                                                                                                                 |
| `has_image`             | `bool`           | A reference image has been set for the session. Optional — with no image the model generates from the prompt alone (text-to-video).                            |
| `has_prompt`            | `bool`           | A prompt has been set for the session.                                                                                                                         |
| `current_prompt`        | `string \| null` | The prompt currently driving generation, or `null` if none set. Match against the scene library to drive the steering UI.                                      |
| `current_chunk`         | `int`            | Zero-based index of the last completed chunk. `0` before the first chunk and back to `0` on `reset`.                                                           |
| `available_resolutions` | `string[]`       | The delivery resolutions this deployment offers, in its configured order. The valid inputs to `set_resolution`. Render the picker from this — never hard-code. |
| `resolution`            | `string`         | The delivery resolution the next `start` will use. A running generation keeps the resolution it started with.                                                  |
| `seed`                  | `int`            | The session's current seed field. The seed driving a running run was captured at `start`.                                                                      |
| `audio_prompt`          | `string \| null` | The sound description currently conditioning the audio, or `null` (default, or cleared). Always `null` on a deployment with no `main_audio`.                   |
| `audio_enabled`         | `bool`           | Whether the next `start` will generate sound. A running generation keeps the setting it started with.                                                          |

**Example handler (broadcasts only):**

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  reactor.on("message", (msg) => {
    switch (msg.type) {
      case "state":
        console.log(`chunk ${msg.data.current_chunk} · ${msg.data.current_prompt ?? ""}`);
        break;
      case "generation_started":
        console.log(`run: ${msg.data.max_chunks} chunks at ${msg.data.resolution}`);
        break;
      case "command_error":
        console.error(`${msg.data.command} rejected: ${msg.data.reason}`);
        break;
    }
  });
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import {
    useViskoOrbisStableState,
    useViskoOrbisStableCommandError,
  } from "@reactor-models/visko-orbis-stable";

  function StateReadout() {
    useViskoOrbisStableState((s) =>
      console.log(`chunk ${s.current_chunk} · ${s.current_prompt ?? ""}`),
    );
    useViskoOrbisStableCommandError((e) => console.error(`${e.command} rejected: ${e.reason}`));
    return null;
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  @reactor.on_message
  def handle(msg):
      data = msg["data"]
      if msg["type"] == "state":
          print(f"chunk {data['current_chunk']} · {data.get('current_prompt') or ''}")
      elif msg["type"] == "command_error":
          print(f"{data['command']} rejected: {data['reason']}")
  ```
</CodeGroup>

## Complete example

Stage a prompt (and optionally an image), wait for the image to decode if you sent one, start, then
steer mid-stream.

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { ViskoOrbisStableModel } from "@reactor-models/visko-orbis-stable";

  const visko = new ViskoOrbisStableModel();

  visko.onChunkComplete(({ chunk_index, active_prompt }) => {
    console.log(`chunk ${chunk_index}: ${active_prompt.slice(0, 40)}…`);
  });
  // Broadcasts only — command acks are read off the awaited call, not this listener.
  visko.onCommandError(({ command, reason }) => {
    console.error(`${command} rejected: ${reason}`);
  });

  await visko.connect(jwt);

  // Text-to-video start — no image. If you set one, wait for image_accepted.
  await visko.setPrompt({
    prompt:
      "A dramatic coastline of black volcanic cliffs at golden hour, huge dark waves rolling in from a blood-orange sea. Cinematic slow aerial motion, a single unbroken take.",
  });
  await visko.start();

  // Live steering — the hero feature. The picture morphs at the next chunk
  // boundary; no start, no reset, no ack wait.
  await visko.setPrompt({
    prompt:
      "The same black volcanic coastline, the same slow aerial camera hugging the cliffs. The sunset is gone — towering charcoal storm clouds now swallow the sky, the waves grow massive and dark. Elemental, dangerous, photorealistic — a single unbroken take.",
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import asyncio, os
  from reactor_sdk import Reactor

  async def main():
      reactor = Reactor(model_name="reactor/visko-orbis-stable",
                        api_key=os.environ["REACTOR_API_KEY"])

      await reactor.connect()

      await reactor.send_command("set_prompt", {
          "prompt": "A dramatic coastline of black volcanic cliffs at golden hour, huge dark waves rolling in from a blood-orange sea. Cinematic slow aerial motion, a single unbroken take.",
      })
      await reactor.send_command("start", {})

      # Live steering — morphs on the next chunk boundary.
      await reactor.send_command("set_prompt", {
          "prompt": "The same black volcanic coastline, the same slow aerial camera hugging the cliffs. The sunset is gone — towering charcoal storm clouds now swallow the sky. Elemental, dangerous, photorealistic — a single unbroken take.",
      })

      await asyncio.Event().wait()

  asyncio.run(main())
  ```
</CodeGroup>
