Skip to content

feat: add full message-level streaming + structured outputs#37

Merged
varin-nair-factory merged 4 commits into
mainfrom
vn/structured-output-sdk
May 14, 2026
Merged

feat: add full message-level streaming + structured outputs#37
varin-nair-factory merged 4 commits into
mainfrom
vn/structured-output-sdk

Conversation

@varin-nair-factory
Copy link
Copy Markdown
Contributor

@varin-nair-factory varin-nair-factory commented May 11, 2026

Summary

This PR makes streaming easier to consume by default and keeps chunk-level streaming available when callers opt in. session.stream() now yields message-level events (assistant, user, tool_call, tool_result, error, result) unless includePartialMessages: true is set, and each turn finishes with a final result event instead of the old turn_complete sentinel.

Structured output is now part of that final result for both run(...) and streaming. The SDK accepts backend structured_output notifications, falls back to parsing assistant JSON when needed, carries structured output validation errors through structuredOutputError, and exports the new public result/message types.

API behavior

Area Before After
run(...) Aggregated stream messages locally after turn_complete Returns the final DroidMessageType.Result emitted by the stream
session.stream() default Chunk-level events like AssistantTextDelta, ToolUse, TurnComplete Message-level events like Assistant, ToolCall, ToolResult, Result
Chunk-level streaming Always on Opt in with includePartialMessages: true
Turn completion DroidMessageType.TurnComplete DroidMessageType.Result
Structured output Parsed from assistant text Prefers backend structured_output, falls back to JSON parsing, includes validation errors

Usage

One-shot run(...)

Before, callers received a locally aggregated result after streaming completed:

const result = await run('Return JSON', { outputFormat });
console.log(result.text);
console.log(result.structuredOutput);

After, run(...) returns the same final Result object that streaming emits:

const result = await run('Return JSON', { outputFormat });

if (result.success) {
  console.log(result.structuredOutput);
}

Structured output and validation failures are available directly on the result:

console.log(result.structuredOutput);      // { name: 'Ada' }
console.log(result.structuredOutputError); // null, or validation error metadata

Default message-level streaming

Before, the default stream was chunk-oriented:

for await (const msg of session.stream(prompt)) {
  if (msg.type === DroidMessageType.AssistantTextDelta) {
    process.stdout.write(msg.text);
  }

  if (msg.type === DroidMessageType.TurnComplete) {
    console.log(msg.tokenUsage);
  }
}

After, the default stream is message-oriented:

for await (const msg of session.stream(prompt)) {
  if (msg.type === DroidMessageType.Assistant) {
    console.log(msg.text);
  }

  if (msg.type === DroidMessageType.Result) {
    console.log(msg.tokenUsage);
  }
}

Opt into chunk-level streaming

Use includePartialMessages: true when you need deltas, completion markers, tool-call chunks, or progress updates:

for await (const msg of session.stream(prompt, { includePartialMessages: true })) {
  if (msg.type === DroidMessageType.AssistantTextDelta) {
    process.stdout.write(msg.text);
  }
}

The stream still ends with DroidMessageType.Result, so final metadata is handled the same way:

if (msg.type === DroidMessageType.Result) {
  console.log(msg.text);
  console.log(msg.structuredOutput);
  console.log(msg.structuredOutputError);
}

Structured output while streaming

Structured output is attached to the final result event, not emitted as a separate public stream event:

for await (const msg of session.stream('Return a person', { outputFormat })) {
  if (msg.type === DroidMessageType.Result) {
    console.log(msg.structuredOutput);
    console.log(msg.structuredOutputError);
  }
}

Implementation notes

  • Adds public message/result types for the new split between default message-level events and optional partial events.
  • Converts create_message notifications into user, assistant, and tool-call messages for the default stream.
  • Tracks per-turn text, token usage, errors, structured output, and emitted messages in StreamStateTracker, then emits a final Result on the non-idle to idle transition.
  • Adds schemas for assistant/thinking completion notifications, tool-call notifications, and structured-output notifications.
  • Updates examples to use DroidMessageType.Result and the structured-output result fields.

Validation

  • npm run typecheck
  • npm run lint
  • npm test
  • npm run format:check

@varin-nair-factory varin-nair-factory self-assigned this May 11, 2026
Base automatically changed from vn/turn-api-simplify to main May 12, 2026 21:24
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@varin-nair-factory varin-nair-factory force-pushed the vn/structured-output-sdk branch from 79bf87d to 345b1a0 Compare May 12, 2026 22:29
varin-nair-factory and others added 2 commits May 12, 2026 17:09
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@varin-nair-factory varin-nair-factory changed the title feat: surface structured output in streams feat: add full message-level streaming + structured outputs May 13, 2026
@factory-ain3sh factory-ain3sh self-requested a review May 13, 2026 00:25
Comment thread src/session.ts
Comment thread src/run.ts Outdated
Comment thread tests/session.test.ts
Copy link
Copy Markdown

@factory-ain3sh factory-ain3sh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted 3 review comments covering the stream overload contract, the no-op includePartialMessages option on run(), and missing coverage for default-vs-partial stream filtering.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@factory-ain3sh factory-ain3sh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior comments are addressed. I reviewed the latest changes and am good with this now.

Note: I could not run local validators in this worktree because dependency repair could not mirror main repo node_modules, but the code-review concerns are resolved.

@varin-nair-factory varin-nair-factory merged commit 9e4fe53 into main May 14, 2026
4 checks passed
@varin-nair-factory varin-nair-factory deleted the vn/structured-output-sdk branch May 14, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants