Skip to content

Streaming

Spectator supports two streaming modes for real-time story generation.

Overview

MethodGranularityReturnsUse Case
stream()Scene-levelAsyncGenerator<Scene, Story>Process each scene as a complete unit
streamText()Token-levelStoryStreamReal-time text display, typing effects

Scene-Level Streaming

stream() is an async generator that yields each completed Scene as it's generated:

typescript
const engine = new Engine({ provider: 'anthropic' })

for await (const scene of engine.stream(input)) {
  console.log(`--- ${scene.beat?.name ?? 'Scene'} ---`)
  console.log(scene.text)
  console.log()
}

The generator's return value is the complete Story, but in most cases you'll just consume the scenes.

Token-Level Streaming

streamText() returns a StoryStream that emits fine-grained events as tokens arrive:

typescript
const stream = engine.streamText({
  characters,
  plot,
  instructions: 'Set in modern-day Seoul',
})

for await (const event of stream) {
  switch (event.type) {
    case 'scene-start':
      console.log(`\n--- Scene ${event.sceneIndex + 1}: ${event.beat?.name ?? 'Untitled'} ---\n`)
      break
    case 'text-delta':
      process.stdout.write(event.text)
      break
    case 'scene-complete':
      console.log('\n')
      if (event.scene.summary) {
        console.log(`[Summary: ${event.scene.summary}]`)
      }
      break
  }
}

Stream Events

The StoryStream emits three event types:

EventFieldsWhen
scene-startsceneIndex, beat?A new scene begins generating
text-deltatext, sceneIndexA chunk of text arrives
scene-completescene, sceneIndexA scene is fully generated and analyzed
typescript
type StreamEvent =
  | { type: 'scene-start'; sceneIndex: number; beat?: BeatData }
  | { type: 'text-delta'; text: string; sceneIndex: number }
  | { type: 'scene-complete'; scene: Scene; sceneIndex: number }

Accessing the Completed Story

After the stream is fully consumed, access the completed Story:

typescript
// Option 1: access after iteration
const stream = engine.streamText(input)
for await (const event of stream) { /* handle events */ }
const story = stream.story

// Option 2: drain the stream and get the story directly
const stream = engine.streamText(input)
const story = await stream.toStory()

WARNING

Accessing stream.story before the stream is fully consumed throws an error. Either iterate through all events first, or use toStory().

Continuation Streaming

Both streaming modes work with story continuation:

typescript
// Scene-level continuation
for await (const scene of engine.continueStream(story, input)) {
  console.log(scene.text)
}

// Token-level continuation
const stream = engine.continueStreamText(story, input)
for await (const event of stream) {
  // handle events...
}

See Story Continuation for more details.

Released under the MIT License.