@@ -87,6 +87,51 @@ export class StreamHandler {
8787 } ) ( ) ;
8888 }
8989
90+ /**
91+ * Create filtered stream from AI SDK fullStream
92+ *
93+ * Filters out reasoning/thinking chunks from the stream.
94+ * Used as safety net to prevent chain-of-thought tokens from leaking to users.
95+ *
96+ * @param result - AI SDK stream result
97+ */
98+ createFilteredFullStream ( result : {
99+ fullStream ?: AsyncIterable < unknown > ;
100+ textStream ?: AsyncIterable < string > ;
101+ } ) : AsyncGenerator < { content : string } > {
102+ type TextDeltaChunk = { type : "text-delta" ; textDelta : string } ;
103+ type ErrorChunk = { type : "error" ; error : Record < string , unknown > } ;
104+
105+ return ( async function * ( ) {
106+ if ( result . fullStream ) {
107+ for await ( const chunk of result . fullStream ) {
108+ if ( chunk && typeof chunk === "object" && "type" in chunk ) {
109+ const chunkType = ( chunk as { type : string } ) . type ;
110+
111+ if ( chunkType === "error" ) {
112+ const errorChunk = chunk as ErrorChunk ;
113+ throw new Error (
114+ `Streaming error: ${
115+ ( errorChunk . error as Record < string , unknown > ) ?. message ||
116+ "Unknown error"
117+ } `,
118+ ) ;
119+ } else if ( chunkType === "text-delta" ) {
120+ yield { content : ( chunk as TextDeltaChunk ) . textDelta } ;
121+ }
122+ // Skip: reasoning, reasoning-signature, redacted-reasoning, source, tool-call, tool-result, etc.
123+ } else if ( typeof chunk === "string" ) {
124+ yield { content : chunk } ;
125+ }
126+ }
127+ } else if ( result . textStream ) {
128+ for await ( const chunk of result . textStream ) {
129+ yield { content : chunk } ;
130+ }
131+ }
132+ } ) ( ) ;
133+ }
134+
90135 /**
91136 * Create standardized stream result - consolidates result structure
92137 */
0 commit comments