Structured output.
Constrain an agent turn to a zod schema with runObject and streamObject.
Structured output constrains the agent's response to a JSON Schema, server-side (grammar-constrained decoding), and validates it client-side against your zod schema. Pass a zod schema, receive a fully-typed object — no prompt engineering, no brittle JSON extraction.
runner.streamObject(agent, input, schema, options?)
Streams exactly like runner.stream() — you receive every AgentRunEvent (including tool-loop and usage events) — plus one final event with the parsed, schema-validated object. Options are AgentRunOptions.
import { z } from 'zod';
const review = z.object({
summary: z.string(),
issues: z.array(z.object({
severity: z.enum(['low', 'medium', 'high']),
description: z.string(),
})),
});
const reviewer = new Agent({
name: 'reviewer',
instructions: 'Review the code the user provides.',
});
for await (const event of runner.streamObject(reviewer, 'Review this function: ...', review)) {
if (event.type === 'message.delta') process.stdout.write(event.delta);
if (event.type === 'object') {
console.log('\nValidated:', event.object); // typed as z.infer<typeof review>
}
}The final event:
{ type: 'object'; object: T } // T = z.infer<typeof schema>runner.runObject(agent, input, schema, options?)
Non-streaming convenience. Like runner.run(), it honours callbacks, throwOnStreamError, retries, and the tool loop — and returns an AgentObjectResult<T>: the full AgentRunResult plus the validated object.
const result = await runner.runObject(
extractor,
'Invoice total $1,250.00 AUD due 30 Sep.',
z.object({ total: z.number(), currency: z.string() }),
);
console.log(result.object.total); // 1250 (number)
console.log(result.object.currency); // "AUD" (string)
console.log(result.finalOutput); // raw JSON text as returned
console.log(result.usage); // token usage, as usualAgentObjectResult<T>
Extends AgentRunResult with one additional field:
| Field | type | description |
|---|---|---|
| object | T | The response text parsed as JSON and validated against your schema. |
Convenience functions
Default-runner-backed, like the other top-level helpers (streamObject / runObject use the default MatildaCore singleton):
import { streamObject, runObject } from '@maincode-ai/matilda-agent-sdk';
const result = await runObject(agent, input, schema, options);
for await (const event of streamObject(agent, input, schema, options)) { /* ... */ }Raw JSON Schema via responseSchema
AgentRunOptions (and therefore SessionOptions) accepts a stringified JSON Schema directly on any run or stream:
const result = await runner.run(agent, 'List three Australian birds.', {
responseSchema: JSON.stringify({
type: 'object',
properties: { birds: { type: 'array', items: { type: 'string' } } },
required: ['birds'],
additionalProperties: false,
}),
});
JSON.parse(result.finalOutput); // guaranteed valid, schema-conforming JSONWith responseSchema set, finalOutput is guaranteed to be valid JSON conforming to the schema — but parsing and validation are up to you.
Safety replace and structured output. Like runText() / streamText(), the object helpers throw SafetyReplaceError when the server replaces the output mid-stream — the replacement text is in .message and the triggering categories in .categories. Token deltas already yielded to your consumer are not rolled back; runObject() is unaffected at the value level, since it throws before returning a result.
Truncation throws. If the stream is truncated before the JSON completes, both helpers throw MatildaObjectParseError with the partial text in .raw. See Error handling.
Stream errors throw. If the server emits an error event mid-stream, streamObject() throws an Error with the server's error code and message, and runObject() throws MatildaAgentStreamError with the partial result attached — matching the behaviour of the text helpers.