Streaming Operations
This doc explains how to author TypeSpec streaming operations and what client emitters will generate for them.
TypeSpec supports three kinds of streaming bodies via the @typespec/streams, @typespec/sse, and @typespec/events packages:
JsonlStream<T>— a newline-delimited JSON stream where every line is a JSON-encodedT.SSEStream<TEvents>— a server-sent event stream (text/event-stream) carrying heterogeneous events described by an@eventsunion.HttpStream<T, ContentType>— a generic streaming body; usingContentType = "text/event-stream"with an@eventsunion activates the same SSE metadata asSSEStream.
JSONL streaming response
Section titled “JSONL streaming response”Use JsonlStream<T> when your service responds with newline-delimited JSON lines, each encoding a value of type T.
import "@typespec/streams";
using TypeSpec.Streams;
model Chunk { text: string;}
@postop generate( @body request: { prompt: string; },): JsonlStream<Chunk>;# unsupported// unsupported// unsupported// unsupported// unsupportedSSE streaming response
Section titled “SSE streaming response”Use SSEStream<TEvents> to define a server-sent event stream. Events are described by a union decorated with @Events.events. Each named variant becomes an SSE event type; unnamed variants produce message events.
import "@typespec/sse";import "@typespec/events";
using TypeSpec.SSE;using TypeSpec.Events;
model ResponseCreated { id: string;}
model ResponseDelta { delta: string;}
@eventsunion ResponseEvents { @contentType("application/json") responseCreated: ResponseCreated,
@contentType("application/json") responseDelta: ResponseDelta,
@contentType("text/plain") @terminalEvent "[DONE]",}
@postop stream( @body request: { prompt: string; },): SSEStream<ResponseEvents>;# unsupported// unsupported// unsupported// unsupported// unsupportedTerminal events
Section titled “Terminal events”Mark a variant with @terminalEvent to indicate that receiving this event signals the end of the stream. Client emitters use this to know when to stop reading and close the connection.
import "@typespec/sse";import "@typespec/events";
using TypeSpec.SSE;using TypeSpec.Events;
model DeltaEvent { delta: string;}
@eventsunion ChatEvents { @contentType("application/json") delta: DeltaEvent,
@contentType("text/plain") @terminalEvent "[DONE]",}
@postop chat( @body request: { message: string; },): SSEStream<ChatEvents>;# unsupported// unsupported// unsupported// unsupported// unsupportedEvent envelopes
Section titled “Event envelopes”When your SSE event wraps a separate payload, use @Events.data on the payload property to distinguish the envelope from its content. Emitters expose both the envelope type and the unwrapped payload type through sseMetadata.
import "@typespec/sse";import "@typespec/events";
using TypeSpec.SSE;using TypeSpec.Events;
model ChatMessage { role: string; content: string;}
@eventsunion ChatEvents { @contentType("application/json") message: { done: false, @data message: ChatMessage, },
@contentType("text/plain") @terminalEvent done: { done: true, },}
@postop chat( @body request: { message: string; },): SSEStream<ChatEvents>;# unsupported// unsupported// unsupported// unsupported// unsupportedUnnamed (message) events
Section titled “Unnamed (message) events”A variant without a name in the union produces an SSE message event — the event: field is omitted on the wire. Use this when your service emits a single homogeneous event type.
import "@typespec/sse";import "@typespec/events";
using TypeSpec.SSE;using TypeSpec.Events;
model Info { desc: string;}
@eventsunion BasicEvents { @contentType("application/json") Info,}
op receive(): SSEStream<BasicEvents>;# unsupported// unsupported// unsupported// unsupported// unsupportedSSE streaming request
Section titled “SSE streaming request”SSEStream can also be used as a request body when the client sends an event stream to the service.
import "@typespec/sse";import "@typespec/events";
using TypeSpec.SSE;using TypeSpec.Events;
model InputEvent { data: string;}
@eventsunion InputEvents { @contentType("application/json") inputEvent: InputEvent,}
op send(stream: SSEStream<InputEvents>): void;# unsupported// unsupported// unsupported// unsupported// unsupportedCustom streaming body
Section titled “Custom streaming body”For content types other than JSONL or text/event-stream, define a model with @streamOf and a @body bytes property.
import "@typespec/streams";
using TypeSpec.Streams;
model AudioChunk { id: string;}
@streamOf(AudioChunk)model AudioStream { @header contentType: "audio/mpeg"; @body body: bytes;}
op synthesize( @body request: { text: string; },): AudioStream;# unsupported// unsupported// unsupported// unsupported// unsupported