@@ -4,6 +4,10 @@ import {
44 createMastraStreamNormalizerHooks ,
55 tokenUsageEventFromChunk ,
66} from "../../../../../../packages/agent-runtime/src/stream/mastra-stream-hooks" ;
7+ import {
8+ normalizeMastraFullStream ,
9+ wrapAgentForAgUi ,
10+ } from "../../../../../../packages/agent-runtime/src/stream/mastra-stream-normalizer" ;
711
812describe ( "tokenUsageEventFromChunk" , ( ) => {
913 it ( "extracts usage, tool_call_id, and tool_name from step output" , ( ) => {
@@ -78,3 +82,95 @@ describe("createMastraStreamNormalizerHooks token emission", () => {
7882 } ) ;
7983 } ) ;
8084} ) ;
85+
86+ describe ( "normalizeMastraFullStream" , ( ) => {
87+ it ( "drops Mastra internal chunks before AG-UI processing" , async ( ) => {
88+ const chunks = normalizeMastraFullStream ( streamFrom ( [
89+ { type : "abort" , payload : { reason : "cancelled" } } ,
90+ { type : "text-start" , payload : { id : "msg-1" } } ,
91+ { type : "finish" , payload : { } } ,
92+ ] ) ) ;
93+
94+ await expect ( collectStream ( chunks ) ) . resolves . toEqual ( [ { type : "finish" , payload : { } } ] ) ;
95+ } ) ;
96+ } ) ;
97+
98+ describe ( "wrapAgentForAgUi abort signal passthrough" , ( ) => {
99+ it ( "passes the run abort signal into stream options" , async ( ) => {
100+ const runAbortController = new AbortController ( ) ;
101+ let receivedSignal : AbortSignal | undefined ;
102+ const agent = wrapAgentForAgUi (
103+ {
104+ async stream ( _messages : unknown , options ?: { abortSignal ?: AbortSignal ; marker ?: string } ) {
105+ receivedSignal = options ?. abortSignal ;
106+ return { fullStream : emptyStream ( ) } ;
107+ } ,
108+ } ,
109+ { } ,
110+ { abortSignal : runAbortController . signal } ,
111+ ) ;
112+
113+ await agent . stream ( [ ] , { marker : "kept" } ) ;
114+
115+ expect ( receivedSignal ) . toBe ( runAbortController . signal ) ;
116+ } ) ;
117+
118+ it ( "merges an existing stream abort signal with the run abort signal" , async ( ) => {
119+ const upstreamAbortController = new AbortController ( ) ;
120+ const runAbortController = new AbortController ( ) ;
121+ let receivedSignal : AbortSignal | undefined ;
122+ const agent = wrapAgentForAgUi (
123+ {
124+ async stream ( _messages : unknown , options ?: { abortSignal ?: AbortSignal } ) {
125+ receivedSignal = options ?. abortSignal ;
126+ return { fullStream : emptyStream ( ) } ;
127+ } ,
128+ } ,
129+ { } ,
130+ { abortSignal : runAbortController . signal } ,
131+ ) ;
132+
133+ await agent . stream ( [ ] , { abortSignal : upstreamAbortController . signal } ) ;
134+ runAbortController . abort ( new Error ( "run cancelled" ) ) ;
135+
136+ expect ( receivedSignal ) . not . toBe ( upstreamAbortController . signal ) ;
137+ expect ( receivedSignal ?. aborted ) . toBe ( true ) ;
138+ } ) ;
139+
140+ it ( "passes the run abort signal into resumeStream options" , async ( ) => {
141+ const runAbortController = new AbortController ( ) ;
142+ let receivedSignal : AbortSignal | undefined ;
143+ const agent = wrapAgentForAgUi (
144+ {
145+ async resumeStream ( _resumeData : unknown , options ?: { abortSignal ?: AbortSignal } ) {
146+ receivedSignal = options ?. abortSignal ;
147+ return { fullStream : emptyStream ( ) } ;
148+ } ,
149+ } ,
150+ { } ,
151+ { abortSignal : runAbortController . signal } ,
152+ ) ;
153+
154+ await agent . resumeStream ( { answer : "yes" } , { } ) ;
155+
156+ expect ( receivedSignal ) . toBe ( runAbortController . signal ) ;
157+ } ) ;
158+ } ) ;
159+
160+ async function * emptyStream ( ) {
161+ yield { type : "finish" , payload : { } } ;
162+ }
163+
164+ async function * streamFrom ( chunks : Array < { payload ?: unknown ; type : string } > ) {
165+ for ( const chunk of chunks ) {
166+ yield chunk ;
167+ }
168+ }
169+
170+ async function collectStream < T > ( stream : AsyncIterable < T > ) : Promise < T [ ] > {
171+ const chunks : T [ ] = [ ] ;
172+ for await ( const chunk of stream ) {
173+ chunks . push ( chunk ) ;
174+ }
175+ return chunks ;
176+ }
0 commit comments