Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ad8b7f1
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 27, 2026
e0cb61b
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 28, 2026
051cadc
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 29, 2026
af8ac1f
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 29, 2026
e68e7d2
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 30, 2026
8305e5b
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 30, 2026
f3da688
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Jan 31, 2026
f8ee413
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 1, 2026
589cbd7
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 2, 2026
e979fdf
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 3, 2026
d4baed2
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 6, 2026
53503a4
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 10, 2026
d51e2e4
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 13, 2026
5123088
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 13, 2026
dd1a307
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 13, 2026
00bcfcd
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 14, 2026
f6a157b
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 15, 2026
816f35b
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 16, 2026
2b87dce
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 17, 2026
146de28
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 18, 2026
0ef6455
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 18, 2026
2d8c690
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 18, 2026
3513471
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 20, 2026
0f5d29e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 20, 2026
c76001e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 23, 2026
f97184f
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 24, 2026
f71c531
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 25, 2026
530e598
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 25, 2026
ea38511
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Feb 27, 2026
19b1a3f
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 2, 2026
757bfdb
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 2, 2026
5fb4cfb
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 3, 2026
cb5adc3
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 3, 2026
046fdbc
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 3, 2026
2a7884f
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 3, 2026
60ecae6
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 5, 2026
71a6e95
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 6, 2026
25fb139
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 9, 2026
8fe6a36
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 9, 2026
d7cb1a3
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 10, 2026
99b34fe
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 10, 2026
e63bfca
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 11, 2026
810af84
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 11, 2026
67142f9
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 11, 2026
58b47e7
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 12, 2026
4dfac66
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 12, 2026
a423b68
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 13, 2026
b8f1f2e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 13, 2026
30fe808
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 13, 2026
972c20b
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 13, 2026
0c882fb
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 13, 2026
e8d3775
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 16, 2026
29a316d
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 16, 2026
70b3e67
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 17, 2026
d97b801
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 17, 2026
e50f179
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 18, 2026
f6f1a4e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 18, 2026
6bc491f
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 18, 2026
bacb19e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 18, 2026
6a97701
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 20, 2026
d64bb0e
Merge branch 'main' of github.com:vercel/workflow
karthikscale3 Mar 20, 2026
b2648b3
fix stream tab for legacy runs
karthikscale3 Mar 20, 2026
333e932
fix stream tab for legacy runs
karthikscale3 Mar 20, 2026
72cb8d1
Merge branch 'main' into karthik/fix-streams-tab
karthikscale3 Mar 21, 2026
defb606
Apply suggestion from @VaguelySerious
VaguelySerious Mar 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curvy-moles-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/web": patch
---

Fix stream display for streams created before version `4.1.0-beta.56`
80 changes: 69 additions & 11 deletions packages/web/app/lib/hooks/use-stream-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ export interface StreamChunk {

const FRAME_HEADER_SIZE = 4;

/**
* Detect stream encoding from the first bytes.
*
* - **framed**: Current format (≥ 4.1.0-beta.56) — 4-byte big-endian length +
* format-prefixed payload (`devl`/`encr`).
* - **legacy**: Older SDK versions (≤ 4.1.0-beta.55) used newline-delimited
* devalue strings with no binary framing.
*/
type StreamEncoding = 'framed' | 'legacy';

function detectEncoding(data: Uint8Array): StreamEncoding {
if (data.length < FRAME_HEADER_SIZE) return 'framed';
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const len = view.getUint32(0, false);
if (len > 0 && len <= 10 * 1024 * 1024) return 'framed';
return 'legacy';
}

export function useStreamReader(
env: EnvMap,
streamId: string | null,
Expand Down Expand Up @@ -48,23 +66,22 @@ export function useStreamReader(

const readStreamData = async () => {
try {
// Get raw binary stream from server (no deserialization on server)
const rawStream = await readStream(
env,
streamId,
undefined,
abortController.signal
);

// Import the CryptoKey if the user has clicked Decrypt
const cryptoKey = encryptionKey
? await importKey(encryptionKey)
: undefined;

// Process length-prefixed frames from the raw stream, deserializing
// and decrypting entirely client-side.
const reader = (rawStream as ReadableStream<Uint8Array>).getReader();
const decoder = new TextDecoder();
let buffer = new Uint8Array(0);
let encoding: StreamEncoding | null = null;
let textRemainder = '';

const appendToBuffer = (data: Uint8Array) => {
const newBuffer = new Uint8Array(buffer.length + data.length);
Expand All @@ -73,18 +90,59 @@ export function useStreamReader(
buffer = newBuffer;
};

const addLegacyLine = (line: string) => {
if (!mounted || !line) return;
const chunkId = chunkIdRef.current++;
let text: string;
try {
const parsed = JSON.parse(line);
text = JSON.stringify(parsed, null, 2);
} catch {
text = line;
}
setChunks((prev) => [...prev, { id: chunkId, text }]);
};

for (;;) {
if (abortController.signal.aborted) break;

const { value, done } = await reader.read();
if (done) {
if (encoding === 'legacy' && textRemainder.trim()) {
addLegacyLine(textRemainder.trim());
textRemainder = '';
}
if (mounted) setIsLive(false);
break;
}

appendToBuffer(value);
// Detect encoding on first read with enough data
if (encoding === null) {
appendToBuffer(value);
if (buffer.length >= FRAME_HEADER_SIZE) {
encoding = detectEncoding(buffer);
}
if (encoding === 'legacy') {
textRemainder = decoder.decode(buffer, { stream: true });
buffer = new Uint8Array(0);
}
} else if (encoding === 'legacy') {
textRemainder += decoder.decode(value, { stream: true });
} else {
appendToBuffer(value);
}

if (encoding === 'legacy') {
const lines = textRemainder.split('\n');
textRemainder = lines.pop() ?? '';
for (const line of lines) {
const trimmed = line.trim();
if (trimmed) addLegacyLine(trimmed);
}
continue;
}

// Process complete frames
// Framed mode: process length-prefixed frames
while (buffer.length >= FRAME_HEADER_SIZE) {
const view = new DataView(
buffer.buffer,
Expand All @@ -93,8 +151,12 @@ export function useStreamReader(
);
const frameLength = view.getUint32(0, false);

if (frameLength === 0 || frameLength > 10 * 1024 * 1024) {
break;
}

if (buffer.length < FRAME_HEADER_SIZE + frameLength) {
break; // Incomplete frame
break;
}

const frameData = buffer.slice(
Expand All @@ -104,7 +166,6 @@ export function useStreamReader(
buffer = buffer.slice(FRAME_HEADER_SIZE + frameLength);

try {
// Check if the frame is encrypted
const { format, payload } = decodeFormatPrefix(frameData);
let dataToHydrate: Uint8Array;

Expand All @@ -119,14 +180,11 @@ export function useStreamReader(
reader.cancel().catch(() => {});
return;
}
// Decrypt to get the inner format-prefixed bytes (e.g., devl+data)
dataToHydrate = await aesGcmDecrypt(cryptoKey, payload);
} else {
// Not encrypted — pass the original frame data (with format prefix)
dataToHydrate = frameData;
}

// hydrateData handles format prefix decoding + devalue parsing
const hydrated = hydrateData(dataToHydrate, revivers);
if (mounted && hydrated !== undefined && hydrated !== null) {
const chunkId = chunkIdRef.current++;
Expand Down
Loading