Skip to content

Conversation

@ethanndickson
Copy link
Member

@ethanndickson ethanndickson commented Nov 28, 2025

Problem

After the ORPC migration (commit 3ee72886), streaming message content appeared delayed:

  • βœ… Token count & speed updates live
  • βœ… Message item (model name, time) appears immediately
  • ❌ Actual message content is delayed by several seconds
messagedelays.mp4

Root Cause: Zod Union Validation Order

ORPC validates every yielded event against WorkspaceChatMessageSchema. The schema was defined as:

export const WorkspaceChatMessageSchema = z.union([
  MuxMessageSchema,                    // ← Tried FIRST for every event
  z.discriminatedUnion("type", [...]), // ← Streaming events live here
  ...
]);

The problem: z.union() tries each schema in order until one passes.

For every stream-delta event (20+ per second during streaming):

  1. MuxMessageSchema is tried first β†’ fails (wrong shape: has id/role/parts, stream-delta has type/messageId/delta)
  2. Then discriminatedUnion is checked β†’ finds matching type: "stream-delta" β†’ passes

This failed validation attempt on MuxMessageSchema runs for every single streaming event. With rapid deltas, the overhead accumulates and causes visible delay.

Why Token Count Updated But Content Didn't

Both depend on the same event flow, but token counts are accumulated totals that remain stable once computed. Message content requires constructing new DisplayedMessage objects with accumulated text. The validation overhead delayed the entire pipeline, but cumulative metrics (tokens) appeared more responsive than the content itself.

Solution

Reorder the union to put discriminatedUnion first:

export const WorkspaceChatMessageSchema = z.union([
  z.discriminatedUnion("type", [...]),  // Most streaming events match here instantly (O(1) lookup)
  WorkspaceInitEventSchema,
  MuxMessageSchema,                      // Full messages only on stream-end/history replay
]);

Since streaming events have a type field, discriminatedUnion provides O(1) lookup - no failed validation attempts.

Also consolidated the two separate discriminatedUnion calls into one for cleaner code.

Changes

  • src/common/orpc/schemas/stream.ts: Reordered union members, merged discriminated unions, added explanatory comment

Generated with mux

Reorder WorkspaceChatMessageSchema union to put discriminatedUnion first.

Before: MuxMessageSchema was tried first for every streaming event, but
stream-delta/stream-start/etc. don't match its shape (they have 'type'
field, MuxMessage has 'id'/'role'/'parts'). This caused a failed
validation attempt on every single event during streaming.

After: discriminatedUnion is tried first, giving O(1) lookup by 'type'
field for the most frequent events. MuxMessageSchema moves to last since
full messages are only sent on stream-end and history replay.

Also consolidated the two separate discriminatedUnions into one.
@ethanndickson ethanndickson added this pull request to the merge queue Nov 28, 2025
Merged via the queue into main with commit 470e4eb Nov 28, 2025
13 checks passed
@ethanndickson ethanndickson deleted the fix-ipc-message-content-delay branch November 28, 2025 02:56
ethanndickson added a commit that referenced this pull request Nov 28, 2025
Reverts:
- 470e4eb perf: fix streaming content delay from ORPC schema validation (#774)
- b437977 feat: add backend support for soft-interrupts (#767)
- df30cbc fix: use ResultSchema for sendMessage output to prevent field stripping (#773)
- 41c77ef fix: testUtils formatting (#771)
- 3ee7288 refactor: migrate IPC layer to ORPC for type-safe RPC (#763)
github-merge-queue bot pushed a commit that referenced this pull request Nov 28, 2025
Reverts:
- 470e4eb perf: fix streaming content delay from ORPC schema validation
(#774)
- b437977 feat: add backend support for soft-interrupts (#767)
- df30cbc fix: use ResultSchema for sendMessage output to prevent field
stripping (#773)
- 41c77ef fix: testUtils formatting (#771)
- 3ee7288 refactor: migrate IPC layer to ORPC for type-safe RPC (#763)

Due to a huge number of regressions.

_Generated with `mux`_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant