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| Event | Payload | Meaning |
|---|---|---|
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.