Skip to main content

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

model
ChatModel
required
The chat model instance to use for streaming.
messages
Message[]
required
Array of messages in the conversation. Must not be empty.
schema
z.ZodType
required
Zod schema that defines the structure of the object to generate.
schemaName
string
Optional name for the schema.
schemaDescription
string
Optional description of the schema.
temperature
number
Sampling temperature (0-2).
maxTokens
number
Maximum number of tokens to generate.
topP
number
Nucleus sampling parameter (0-1).
reasoning
ReasoningConfig
Configuration for extended thinking/reasoning capabilities.
providerOptions
GenerateProviderOptions
Provider-specific options, namespaced by provider name.
signal
AbortSignal
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:
  • Messages array is empty
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);
  }
}