Skip to content

Commit 3faa759

Browse files
committed
feat(stream): add method for filtering reasoning chunks from the ai sdk stream
1 parent 3c5c9a2 commit 3faa759

4 files changed

Lines changed: 380 additions & 3 deletions

File tree

src/lib/core/baseProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export abstract class BaseProvider implements AIProvider {
6161

6262
// Composition modules - Single Responsibility Principle
6363
private readonly messageBuilder: MessageBuilder;
64-
private readonly streamHandler: StreamHandler;
64+
protected readonly streamHandler: StreamHandler;
6565
private readonly generationHandler: GenerationHandler;
6666
private readonly telemetryHandler: TelemetryHandler;
6767
private readonly utilities: Utilities;

src/lib/core/modules/StreamHandler.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/

src/lib/providers/googleVertex.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -983,8 +983,8 @@ export class GoogleVertexProvider extends BaseProvider {
983983

984984
timeoutController?.cleanup();
985985

986-
// Transform string stream to content object stream using BaseProvider method
987-
const transformedStream = this.createTextStream(result);
986+
const transformedStream =
987+
this.streamHandler.createFilteredFullStream(result);
988988

989989
// Track tool calls and results for streaming
990990
const toolCalls: Array<{

0 commit comments

Comments
 (0)