Polimorf Docs

Streaming

Stream a Polimorf generation token-by-token over Server-Sent Events, with a typed event contract and cancellation.

Streaming lets you render a response as it is produced instead of waiting for the whole generation. Every streaming endpoint returns Server-Sent Events (SSE); the SDK decodes them into typed events.

The event sequence

A stream emits incremental content, then a final usage count and a done marker — or a terminal error:

delta  →  delta  →  …  →  usage  →  done
EventPayloadMeaning
delta{ content: string }A chunk of generated text. Concatenate in order.
usage{ usage: { inputTokens, outputTokens, totalTokens } }Final token accounting.
done{ finishReason }The generation finished. See finish reasons below.
error{ error: { code, message, retryable, providerName? } }A terminal provider error; the stream ends.

Finish reasons

stop · length · tool_calls · content_filter · error

Consuming a stream with the SDK

Both client.runtime.stream(...) and client.assistant(slug).stream(...) return an AsyncIterable of typed events. Iterate with for await:

import { createClient } from '@polimorfapp/sdk';

const client = createClient();

const stream = client.runtime.stream({
  providerName: 'openai',
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Write a haiku about the sea.' }],
});

let text = '';
for await (const event of stream) {
  if (event.type === 'delta') {
    text += event.content;
    process.stdout.write(event.content);
  } else if (event.type === 'done') {
    console.log('\nfinish:', event.finishReason);
  } else if (event.type === 'error') {
    console.error('\nprovider error:', event.error.code, event.error.message);
  }
}

A provider failure is delivered as a terminal error event, mirroring the wire contract — it is not thrown. Only a transport failure (network, timeout, abort) throws a PolimorfError. Handle both: branch on the error event and wrap the loop in try/catch.

Cancellation

Pass an AbortSignal to stop a stream in flight — for example when a user navigates away or a request times out:

const controller = new AbortController();

// Abort after 5 seconds.
setTimeout(() => controller.abort(), 5_000);

for await (const event of client
  .assistant('support')
  .stream(
    { input: 'Summarize our refund policy.' },
    { signal: controller.signal },
  )) {
  if (event.type === 'delta') process.stdout.write(event.content);
}

The caller signal is composed with the client's own request timeout, so whichever fires first ends the stream.

Raw SSE (without the SDK)

The wire format is standard SSE — each frame has an event: name and a JSON data: payload:

event: delta
data: {"content":"Hello"}

event: delta
data: {"content":", world"}

event: usage
data: {"inputTokens":12,"outputTokens":3,"totalTokens":15}

event: done
data: {"finishReason":"stop"}

Unrecognized event names should be ignored, so a future server-side event type never breaks an existing client.