Skip to content

Commit fadd2eb

Browse files
committed
revert and fix tests
1 parent ca4bafa commit fadd2eb

File tree

2 files changed

+21
-65
lines changed

2 files changed

+21
-65
lines changed

packages/cloudflare/src/request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function wrapRequestHandler(
117117
}
118118

119119
// Classify response to detect actual streaming
120-
const classification = await classifyResponseStreaming(res);
120+
const classification = classifyResponseStreaming(res);
121121

122122
if (classification.isStreaming && classification.response.body) {
123123
// Streaming response detected - monitor consumption to keep span alive

packages/cloudflare/src/utils/streaming.ts

Lines changed: 20 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,81 +6,37 @@ export type StreamingGuess = {
66
/**
77
* Classifies a Response as streaming or non-streaming.
88
*
9-
* Uses multiple heuristics:
9+
* Heuristics:
1010
* - No body → not streaming
11-
* - Content-Type: text/event-stream → streaming
12-
* - Content-Length header present → not streaming
13-
* - Otherwise: attempts immediate read with timeout to detect behavior
14-
* - Timeout (no data ready) → not streaming (typical SSR/buffered response)
15-
* - Stream empty (done) → not streaming
16-
* - Got data without Content-Length → streaming (e.g., Vercel AI SDK)
17-
* - Got data with Content-Length → not streaming
11+
* - Known streaming Content-Types → streaming (SSE, NDJSON, JSON streaming)
12+
* - text/plain without Content-Length → streaming (some AI APIs)
13+
* - Otherwise → not streaming (conservative default, including HTML/SSR)
1814
*
19-
* The timeout prevents blocking on responses that are being generated (like SSR),
20-
* while still detecting true streaming responses that produce data immediately.
21-
*
22-
* Note: Probing will tee() the stream and return a new Response object.
23-
*
24-
* @param res - The Response to classify
25-
* @returns Classification result with safe-to-return Response
15+
* We avoid probing the stream to prevent blocking on transform streams (like injectTraceMetaTags)
16+
* or SSR streams that may not have data ready immediately.
2617
*/
27-
export async function classifyResponseStreaming(res: Response): Promise<StreamingGuess> {
18+
export function classifyResponseStreaming(res: Response): StreamingGuess {
2819
if (!res.body) {
2920
return { response: res, isStreaming: false };
3021
}
3122

3223
const contentType = res.headers.get('content-type') ?? '';
3324
const contentLength = res.headers.get('content-length');
3425

35-
// Fast path: Server-Sent Events
36-
if (/^text\/event-stream\b/i.test(contentType)) {
26+
// Streaming: Known streaming content types
27+
// - text/event-stream: Server-Sent Events (Vercel AI SDK, real-time APIs)
28+
// - application/x-ndjson, application/ndjson: Newline-delimited JSON
29+
// - application/stream+json: JSON streaming
30+
// - text/plain (without Content-Length): Some AI APIs use this for streaming text
31+
if (
32+
/^text\/event-stream\b/i.test(contentType) ||
33+
/^application\/(x-)?ndjson\b/i.test(contentType) ||
34+
/^application\/stream\+json\b/i.test(contentType) ||
35+
(/^text\/plain\b/i.test(contentType) && !contentLength)
36+
) {
3737
return { response: res, isStreaming: true };
3838
}
3939

40-
// Fast path: Content-Length indicates buffered response
41-
if (contentLength && /^\d+$/.test(contentLength)) {
42-
return { response: res, isStreaming: false };
43-
}
44-
45-
// Probe the stream by trying to read first chunk immediately with a timeout
46-
// After tee(), must use the teed stream (original is locked)
47-
const [probeStream, passStream] = res.body.tee();
48-
const reader = probeStream.getReader();
49-
50-
try {
51-
// Use a short timeout to avoid blocking on responses that aren't immediately ready
52-
// Streaming responses (like Vercel AI) typically start producing data right away
53-
// Buffered responses (like Remix SSR) will block until content is generated
54-
const PROBE_TIMEOUT_MS = 10;
55-
56-
const timeoutPromise = new Promise<{ done: boolean; value?: unknown; timedOut: true }>(resolve => {
57-
setTimeout(() => resolve({ done: false, value: undefined, timedOut: true }), PROBE_TIMEOUT_MS);
58-
});
59-
60-
const readPromise = reader.read().then(result => ({ ...result, timedOut: false as const }));
61-
const result = await Promise.race([readPromise, timeoutPromise]);
62-
63-
reader.releaseLock();
64-
65-
const teededResponse = new Response(passStream, res);
66-
67-
if (result.timedOut) {
68-
// Timeout means data isn't immediately available - likely a buffered response
69-
// being generated (like SSR). Treat as non-streaming.
70-
return { response: teededResponse, isStreaming: false };
71-
}
72-
73-
if (result.done) {
74-
// Stream completed immediately - buffered (empty body)
75-
return { response: teededResponse, isStreaming: false };
76-
}
77-
78-
// Got data immediately without Content-Length - likely streaming
79-
// Got data immediately with Content-Length - buffered
80-
return { response: teededResponse, isStreaming: contentLength == null };
81-
} catch {
82-
reader.releaseLock();
83-
// Error reading - treat as non-streaming to be safe
84-
return { response: new Response(passStream, res), isStreaming: false };
85-
}
40+
// Default: treat as non-streaming
41+
return { response: res, isStreaming: false };
8642
}

0 commit comments

Comments
 (0)