Mastra
Mastra's documented progressive structured-output surface is objectStream. Prefer it directly when its partial-object cadence is sufficient, and use result.object as the validated final value.
Mastra's documented progressive structured-output surface is objectStream. Prefer it directly when
its partial-object cadence is sufficient, and use result.object as the validated final value.
import { Agent } from "@mastra/core/agent"
import { z } from "zod"
const itinerarySchema = z.object({
title: z.string(),
stops: z.array(z.object({ name: z.string(), note: z.string().nullable() }))
})
const agent = new Agent({
id: "itinerary",
name: "Itinerary",
model: "openai/gpt-5.6-luna",
instructions: "Return the requested structured itinerary."
})
const result = await agent.stream("Plan a weekend.", {
structuredOutput: { errorStrategy: "strict", schema: itinerarySchema }
})
for await (const partial of result.objectStream) {
renderProgress(partial)
}
const finalOutput = await result.objectGuard the compatibility path
Mastra describes textStream as natural-language text, not a general raw-JSON contract. A pinned
Mastra/provider combination can currently expose the structured response as JSON text. Schema Stream
can add incomplete-string updates, completion events, and snapshot policies only when that contract
is checked and allowed to fail closed.
import { SchemaStream, type SchemaStreamSource } from "schema-stream"
async function* requireJsonObjectText(
source: SchemaStreamSource<string>
): AsyncIterable<string> {
let sawJsonStart = false
for await (const chunk of source) {
if (!sawJsonStart) {
const candidate = chunk.trimStart()
if (candidate.length > 0) {
if (!candidate.startsWith("{")) {
throw new Error("Mastra textStream did not begin with a JSON object")
}
sawJsonStart = true
}
}
yield chunk
}
if (!sawJsonStart) {
throw new Error("Mastra textStream did not contain a JSON object")
}
}
const parser = new SchemaStream(itinerarySchema)
for await (const snapshot of parser.iterate(requireJsonObjectText(result.textStream))) {
renderFineGrainedProgress(snapshot)
}
const finalOutput = await result.objectThis guard only checks that the stream starts like a JSON object; the parser and final Mastra result still provide the meaningful end-to-end checks. Do not silently fall back from prose to partial JSON parsing.
Verify the pinned lane
examples/mastra.ts is the executable compatibility canary. Its
credential-free model runs through Mastra's real Agent.stream() orchestration, verifies a nested
completion-driven decision, and compares the final Schema Stream snapshot with result.object:
bun run example:mastra
bun test tests/sdk-runtime.test.tsThe repository's weekly live lane pins the tested Mastra and OpenAI model combination:
SCHEMA_STREAM_LIVE_E2E=1 \
SCHEMA_STREAM_LIVE_PROVIDER=mastra \
SCHEMA_STREAM_MASTRA_MODEL=openai/gpt-5.6-luna \
bun run test:liveThat lane requires OPENAI_API_KEY in the server environment. It is a canary for the pinned path,
not a promise that every Mastra provider emits JSON through textStream.
Vercel AI SDK
Use Schema Stream with the Vercel AI SDK when you need finer updates than partialOutputStream provides, schema-derived placeholders before fields arrive, or completion events for individual JSON values. Feed streamText().textStream to the parser and keep result.output as the authoritative structured result.
Bun WebSocket
For an interactive browser application, keep the provider SDK, credentials, Schema Stream, and authoritative validation on Bun. Send one complete application-level JSON document per WebSocket message so the browser only needs ordinary JSON.parse, revision checks, and rendering.