From 1e28e2702aeff0ba0273c6673f611d2995e6629c Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Tue, 19 May 2026 19:43:54 +0200 Subject: [PATCH 1/2] fix(ai): restore StructuredOutputStream assignability to AsyncIterable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `StructuredOutputStartEvent`, `StructuredOutputCompleteEvent`, `ApprovalRequestedEvent`, and `ToolInputAvailableEvent` declared their shape with `extends Omit`. 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` matches `AsyncIterable`. --- ...uctured-output-stream-sse-assignability.md | 9 ++++++++ packages/typescript/ai/src/types.ts | 21 +++++-------------- .../ai/tests/chat-result-types.test.ts | 8 +++++++ 3 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 .changeset/fix-structured-output-stream-sse-assignability.md diff --git a/.changeset/fix-structured-output-stream-sse-assignability.md b/.changeset/fix-structured-output-stream-sse-assignability.md new file mode 100644 index 000000000..f75f68002 --- /dev/null +++ b/.changeset/fix-structured-output-stream-sse-assignability.md @@ -0,0 +1,9 @@ +--- +"@tanstack/ai": patch +--- + +fix(ai): restore `StructuredOutputStream` assignability to `AsyncIterable` so it can be passed to `toServerSentEventsResponse` + +`StructuredOutputStartEvent`, `StructuredOutputCompleteEvent`, `ApprovalRequestedEvent`, and `ToolInputAvailableEvent` declared their shape with `extends Omit`. 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` (allowed: narrower types of declared properties), which keeps `type: 'CUSTOM'` intact and preserves the existing discriminated-narrowing patterns. diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 3ffe72ffe..6e1ec2b5d 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -1146,10 +1146,8 @@ export interface CustomEvent extends AGUICustomEvent { * } * ``` */ -export interface StructuredOutputCompleteEvent extends Omit< - CustomEvent, - 'name' | 'value' -> { +export interface StructuredOutputCompleteEvent + extends CustomEvent { name: 'structured-output.complete' value: { object: T; raw: string; reasoning?: string } } @@ -1162,10 +1160,7 @@ export interface StructuredOutputCompleteEvent extends Omit< * `messageId` the deltas will be tagged with so the routing decision can be * made per-message rather than globally. */ -export interface StructuredOutputStartEvent extends Omit< - CustomEvent, - 'name' | 'value' -> { +export interface StructuredOutputStartEvent extends CustomEvent { name: 'structured-output.start' value: { messageId: string } } @@ -1177,10 +1172,7 @@ export interface StructuredOutputStartEvent extends Omit< * (the agent-loop branch of `runStreamingStructuredOutputImpl` in * `activities/chat/index.ts` forwards CUSTOM events from `TextEngine.run()`). */ -export interface ApprovalRequestedEvent extends Omit< - CustomEvent, - 'name' | 'value' -> { +export interface ApprovalRequestedEvent extends CustomEvent { name: 'approval-requested' value: { toolCallId: string @@ -1196,10 +1188,7 @@ export interface ApprovalRequestedEvent extends Omit< * will not fire for that run. Shape fixed by the agent-loop forwarding in * `runStreamingStructuredOutputImpl` in `activities/chat/index.ts`. */ -export interface ToolInputAvailableEvent extends Omit< - CustomEvent, - 'name' | 'value' -> { +export interface ToolInputAvailableEvent extends CustomEvent { name: 'tool-input-available' value: { toolCallId: string diff --git a/packages/typescript/ai/tests/chat-result-types.test.ts b/packages/typescript/ai/tests/chat-result-types.test.ts index 5dd9d1eb9..b11cd839a 100644 --- a/packages/typescript/ai/tests/chat-result-types.test.ts +++ b/packages/typescript/ai/tests/chat-result-types.test.ts @@ -79,6 +79,14 @@ describe('chat() return type', () => { }) }) + describe('StructuredOutputStream assignability', () => { + it('is assignable to AsyncIterable (toServerSentEventsResponse input)', () => { + expectTypeOf< + StructuredOutputStream + >().toMatchTypeOf>() + }) + }) + describe('without outputSchema', () => { it('stream: true → AsyncIterable', () => { expectTypeOf>().toEqualTypeOf< From 69916a880763e0f4e7ebd73ff0e3d4864fff511b Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 17:45:34 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- .../fix-structured-output-stream-sse-assignability.md | 2 +- packages/typescript/ai/src/types.ts | 5 +++-- packages/typescript/ai/tests/chat-result-types.test.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.changeset/fix-structured-output-stream-sse-assignability.md b/.changeset/fix-structured-output-stream-sse-assignability.md index f75f68002..70fa7ad9b 100644 --- a/.changeset/fix-structured-output-stream-sse-assignability.md +++ b/.changeset/fix-structured-output-stream-sse-assignability.md @@ -1,5 +1,5 @@ --- -"@tanstack/ai": patch +'@tanstack/ai': patch --- fix(ai): restore `StructuredOutputStream` assignability to `AsyncIterable` so it can be passed to `toServerSentEventsResponse` diff --git a/packages/typescript/ai/src/types.ts b/packages/typescript/ai/src/types.ts index 6e1ec2b5d..fa8d3d005 100644 --- a/packages/typescript/ai/src/types.ts +++ b/packages/typescript/ai/src/types.ts @@ -1146,8 +1146,9 @@ export interface CustomEvent extends AGUICustomEvent { * } * ``` */ -export interface StructuredOutputCompleteEvent - extends CustomEvent { +export interface StructuredOutputCompleteEvent< + T = unknown, +> extends CustomEvent { name: 'structured-output.complete' value: { object: T; raw: string; reasoning?: string } } diff --git a/packages/typescript/ai/tests/chat-result-types.test.ts b/packages/typescript/ai/tests/chat-result-types.test.ts index b11cd839a..2e8ad1aeb 100644 --- a/packages/typescript/ai/tests/chat-result-types.test.ts +++ b/packages/typescript/ai/tests/chat-result-types.test.ts @@ -81,9 +81,9 @@ describe('chat() return type', () => { describe('StructuredOutputStream assignability', () => { it('is assignable to AsyncIterable (toServerSentEventsResponse input)', () => { - expectTypeOf< - StructuredOutputStream - >().toMatchTypeOf>() + expectTypeOf>().toMatchTypeOf< + AsyncIterable + >() }) })