Skip to content

fix(ai): restore StructuredOutputStream assignability to AsyncIterable<StreamChunk>#587

Merged
AlemTuzlak merged 2 commits into
mainfrom
fix-structured-output-stream-sse-assignability
May 19, 2026
Merged

fix(ai): restore StructuredOutputStream assignability to AsyncIterable<StreamChunk>#587
AlemTuzlak merged 2 commits into
mainfrom
fix-structured-output-stream-sse-assignability

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • StructuredOutputStartEvent, StructuredOutputCompleteEvent, ApprovalRequestedEvent, and ToolInputAvailableEvent declared their shape with extends Omit<CustomEvent, 'name' | 'value'>. Because CustomEvent is inferred from a zod passthrough schema, it carries a [k: string]: unknown index signature, and Omit on a type with a string index signature collapses every surviving property to unknown — including type: 'CUSTOM'. That broke union assignability against AGUIEvent / StreamChunk, so toServerSentEventsResponse(stream) failed to typecheck against the stream returned by chat({ outputSchema, stream: true }) — the exact pattern documented in docs/structured-outputs/streaming.md.
  • Switched the four interfaces to extends CustomEvent with refined name/value (narrower types of inherited properties are allowed). type: 'CUSTOM' is preserved and existing discriminated-narrowing (chunk.type === 'CUSTOM' && chunk.name === '...') continues to give a fully typed value.
  • Added a type-level regression assertion in chat-result-types.test.ts that pins StructuredOutputStream<T> to AsyncIterable<StreamChunk> so this can't silently regress again.

Reproducer

Before the fix:

import { chat, toServerSentEventsResponse } from '@tanstack/ai'

const stream = chat({ adapter, messages, outputSchema, stream: true })
return toServerSentEventsResponse(stream)
// TS2345: Argument of type 'StructuredOutputStream<{...}>' is not
// assignable to parameter of type 'AsyncIterable<AGUIEvent>'.
//   Type 'StructuredOutputStartEvent' is missing the following
//   properties from type 'ReasoningEncryptedValueEvent': type,
//   subtype, entityId, encryptedValue

After the fix this compiles. Runtime behaviour is unchanged — this is a pure type-level bug fix.

Test plan

  • pnpm test:lib — 832 tests pass in @tanstack/ai
  • pnpm test:types — 32 projects pass
  • New regression assertion exercises StructuredOutputStream<Person>AsyncIterable<StreamChunk>
  • Verified discrimination still works on structured-output.complete, structured-output.start, approval-requested, tool-input-available

Summary by CodeRabbit

  • Bug Fixes

    • Fixed TypeScript type assignability issues when using structured output streaming with the chat API, ensuring proper type compatibility for stream chunks with strict type checking enabled.
  • Tests

    • Added type-level test to verify structured output stream assignability.

Review Change Stack

…e<StreamChunk>

`StructuredOutputStartEvent`, `StructuredOutputCompleteEvent`,
`ApprovalRequestedEvent`, and `ToolInputAvailableEvent` declared their shape
with `extends Omit<CustomEvent, 'name' | 'value'>`. Because `CustomEvent` is
inferred from a zod `passthrough` schema it carries a `[k: string]: unknown`
index signature, and `Omit` on a type with a `string` index signature
collapses every surviving property to `unknown` — including `type: 'CUSTOM'`.
That broke union assignability against `AGUIEvent`/`StreamChunk`, so
`toServerSentEventsResponse(stream)` failed to typecheck against streams
returned by `chat({ outputSchema, stream: true })`.

Switched to `extends CustomEvent` with refined `name`/`value` (a narrower
type for an inherited property is allowed), preserves `type: 'CUSTOM'` and
the existing discriminated-narrowing patterns.

Added a type-level regression test asserting
`StructuredOutputStream<T>` matches `AsyncIterable<StreamChunk>`.
@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0591d84-01d7-49f8-a5b2-6b66cd72f69b

📥 Commits

Reviewing files that changed from the base of the PR and between dca370b and 69916a8.

📒 Files selected for processing (3)
  • .changeset/fix-structured-output-stream-sse-assignability.md
  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai/tests/chat-result-types.test.ts

📝 Walkthrough

Walkthrough

This PR restores TypeScript assignability for StructuredOutputStream<T> as an AsyncIterable<StreamChunk> by refactoring four structured-output event interfaces to extend CustomEvent directly instead of using Omit, which was collapsing property types to unknown due to CustomEvent's string index signature from a passthrough Zod schema. A type-level test validates the fix.

Changes

Event Type Assignability Fix

Layer / File(s) Summary
Fix Documentation
.changeset/fix-structured-output-stream-sse-assignability.md
Changeset explains that Omit<CustomEvent, 'name' | 'value'> caused surviving properties to collapse to unknown, breaking union assignability. The fix uses extends CustomEvent with refined name/value to restore discriminated narrowing.
Event Interface Type Refinement
packages/typescript/ai/src/types.ts
StructuredOutputCompleteEvent<T>, StructuredOutputStartEvent, ApprovalRequestedEvent, and ToolInputAvailableEvent now extend CustomEvent directly, each preserving their literal name and typed value properties.
Assignability Type Test
packages/typescript/ai/tests/chat-result-types.test.ts
New type-level test asserts that StructuredOutputStream<Person> is assignable to AsyncIterable<StreamChunk>, validating that the stream type correctly satisfies the consumer interface.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A hop through the types so fine,
Where Omit and Unions align,
CustomEvent now gleams with care,
Assignability's back in the air!
No unknown to make us despair!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: restoring type assignability for StructuredOutputStream to AsyncIterable.
Description check ✅ Passed The PR description comprehensively covers the problem, solution, reproducer, and test plan, addressing all key aspects despite missing the template's exact structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-structured-output-stream-sse-assignability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

1 package(s) bumped directly, 18 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/ai 0.19.0 → 0.19.1 Changeset
@tanstack/ai-client 0.11.0 → 0.11.1 Dependent
@tanstack/ai-code-mode 0.1.13 → 0.1.14 Dependent
@tanstack/ai-code-mode-skills 0.1.13 → 0.1.14 Dependent
@tanstack/ai-devtools-core 0.3.30 → 0.3.31 Dependent
@tanstack/ai-event-client 0.3.3 → 0.3.4 Dependent
@tanstack/ai-fal 0.7.6 → 0.7.7 Dependent
@tanstack/ai-isolate-cloudflare 0.2.4 → 0.2.5 Dependent
@tanstack/ai-isolate-node 0.1.13 → 0.1.14 Dependent
@tanstack/ai-isolate-quickjs 0.1.13 → 0.1.14 Dependent
@tanstack/ai-preact 0.6.25 → 0.6.26 Dependent
@tanstack/ai-react 0.11.0 → 0.11.1 Dependent
@tanstack/ai-solid 0.10.0 → 0.10.1 Dependent
@tanstack/ai-svelte 0.10.0 → 0.10.1 Dependent
@tanstack/ai-vue 0.10.1 → 0.10.2 Dependent
@tanstack/ai-vue-ui 0.1.37 → 0.1.38 Dependent
@tanstack/preact-ai-devtools 0.1.34 → 0.1.35 Dependent
@tanstack/react-ai-devtools 0.2.34 → 0.2.35 Dependent
@tanstack/solid-ai-devtools 0.2.34 → 0.2.35 Dependent

@nx-cloud

nx-cloud Bot commented May 19, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 69916a8

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 4m 4s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-19 17:51:27 UTC

@pkg-pr-new

pkg-pr-new Bot commented May 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@587

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@587

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@587

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@587

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@587

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@587

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@587

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@587

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@587

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@587

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@587

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@587

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@587

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@587

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@587

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@587

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@587

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@587

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@587

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@587

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@587

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@587

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@587

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@587

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@587

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@587

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@587

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@587

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@587

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@587

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@587

commit: 69916a8

@AlemTuzlak AlemTuzlak merged commit 617b5b5 into main May 19, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the fix-structured-output-stream-sse-assignability branch May 19, 2026 18:04
@github-actions github-actions Bot mentioned this pull request May 19, 2026
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.

1 participant