Overview
The streamObject() function streams structured objects from chat models in real-time, with progressive updates validated against a Zod schema. This is ideal for displaying partial results as they’re being generated.
Function signature
export async function streamObject<TSchema extends z.ZodType>(
params: StreamObjectParams<TSchema>
): Promise<ObjectStream<TSchema>>
export type StreamObjectParams<TSchema extends z.ZodType> =
StreamObjectOptions<TSchema> & {
model: ChatModel;
};
Parameters
The chat model instance to use for streaming.
Array of messages in the conversation. Must not be empty.
Zod schema that defines the structure of the object to generate.
Optional name for the schema.
Optional description of the schema.
Sampling temperature (0-2).
Maximum number of tokens to generate.
Nucleus sampling parameter (0-1).
Configuration for extended thinking/reasoning capabilities.
Provider-specific options, namespaced by provider name.
AbortSignal for cancelling the stream.
Return value
Returns a Promise<ObjectStream<TSchema>>. ObjectStream is an async iterable of ObjectStreamEvent objects with two additional properties:
type ObjectStream<TSchema extends z.ZodType> = AsyncIterable<
ObjectStreamEvent<TSchema>
> & {
readonly result: Promise<GenerateObjectResult<TSchema>>;
readonly events: Promise<readonly ObjectStreamEvent<TSchema>[]>;
};
result
Promise<GenerateObjectResult<TSchema>>
Resolves with the final validated object result when the stream completes. Rejects on abort or upstream failure.
events
Promise<readonly ObjectStreamEvent<TSchema>[]>
Resolves with all observed events, including abort and failure cases.
The HTTP request starts as soon as you create the stream. You do not need to iterate before the model begins responding.
ObjectStreamEvent types
object-delta
{ type: 'object-delta'; text: string }
Emitted for each chunk of the JSON object being generated.
object
{ type: 'object'; object: z.infer<TSchema> }
Emitted once, when the full object payload has been validated.
finish
{ type: 'finish'; finishReason: FinishReason; usage: ChatUsage }
Emitted when streaming completes with final metadata.
Examples
Basic streaming object
import { streamObject } from '@core-ai/core-ai';
import { createOpenAI } from '@core-ai/openai';
import { z } from 'zod';
const openai = createOpenAI();
const model = openai.chatModel('gpt-5-mini');
const schema = z.object({
name: z.string(),
age: z.number(),
bio: z.string()
});
const objectStream = await streamObject({
model,
messages: [
{ role: 'user', content: 'Generate a character profile' }
],
schema
});
for await (const event of objectStream) {
if (event.type === 'object-delta') {
process.stdout.write(event.text);
} else if (event.type === 'object') {
console.log('\n\nComplete object:', event.object);
}
}
Progressive UI updates
const schema = z.object({
tasks: z.array(
z.object({
title: z.string(),
description: z.string(),
priority: z.enum(['low', 'medium', 'high'])
})
)
});
const objectStream = await streamObject({
model,
messages: [
{ role: 'user', content: 'Create a project task list' }
],
schema
});
let partialJson = '';
for await (const event of objectStream) {
switch (event.type) {
case 'object-delta':
partialJson += event.text;
updatePreview(partialJson);
break;
case 'object':
displayTasks(event.object.tasks);
break;
case 'finish':
console.log('Tokens used:', event.usage.outputTokens);
break;
}
}
Using .result
const objectStream = await streamObject({
model,
messages: [
{ role: 'user', content: 'Generate a user profile' }
],
schema: z.object({
username: z.string(),
email: z.string().email()
})
});
const finalResult = await objectStream.result;
console.log(finalResult.object);
console.log('Usage:', finalResult.usage);
Using .events
const objectStream = await streamObject({
model,
messages: [{ role: 'user', content: 'Generate a user profile' }],
schema
});
const events = await objectStream.events;
console.log(events.map((event) => event.type));
ObjectStream follows the same lifecycle semantics as ChatStream: .result settles independently of iteration, .events keeps the observed history, and iteration is replayable.
Cancellation
const controller = new AbortController();
const objectStream = await streamObject({
model,
messages: [
{ role: 'user', content: 'Generate a large dataset' }
],
schema: z.object({
data: z.array(z.object({
id: z.number(),
value: z.string()
}))
}),
signal: controller.signal
});
setTimeout(() => controller.abort(), 5000);
try {
for await (const event of objectStream) {
if (event.type === 'object-delta') {
process.stdout.write(event.text);
}
}
} catch (error) {
console.log('\nStream cancelled');
}
Important notes
ObjectStream is replayable: iterating after events have already arrived replays the buffered event history.
The object event is emitted once with the complete validated object. Use object-delta events to show progressive updates while the JSON payload is still streaming.
Error handling
Throws ValidationError if:
May also throw:
CoreAIError if the stream completes without generating an object
ProviderError if the provider returns an error during streaming
May also throw structured output errors:
StructuredOutputValidationError
StructuredOutputParseError
import {
ValidationError,
StructuredOutputValidationError
} from '@core-ai/core-ai';
try {
const objectStream = await streamObject({
model,
messages: [{ role: 'user', content: 'Generate data' }],
schema: z.object({ name: z.string() })
});
for await (const event of objectStream) {
// Process events
}
} catch (error) {
if (error instanceof StructuredOutputValidationError) {
console.error('Validation failed:', error.issues);
} else if (error instanceof ValidationError) {
console.error('Stream failed:', error.message);
}
}