Skip to content

fix(examples-chat): use get_stream_writer (not adispatch_custom_event)#268

Merged
blove merged 1 commit into
mainfrom
claude/genui-streaming-use-stream-writer
May 12, 2026
Merged

fix(examples-chat): use get_stream_writer (not adispatch_custom_event)#268
blove merged 1 commit into
mainfrom
claude/genui-streaming-use-stream-writer

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 12, 2026

Summary

PR #266 fixed the `on_llm_new_token` signature, after which live diagnostics confirmed the handler runs per token and `adispatch_custom_event` completes successfully — yet the SSE stream still carried zero `a2ui-partial` events.

Root cause

`adispatch_custom_event` (langchain_core) and `stream_mode='custom'` (LangGraph) are different layers:

  • langchain_core's dispatched events are visible only via `stream_mode='events'`
  • LangGraph's `custom` channel is fed by `get_stream_writer()` returned from `langgraph.config`

The transport already requests `stream_mode='custom'`, but the handler was writing to the wrong sink.

Fix

Replace `adispatch_custom_event` with `get_stream_writer()`. The writer is contextvar-scoped to the currently-executing LangGraph node and inherited by nested callbacks, so the handler can call it from inside the LLM's callback chain. Payload shape `{name, data}` matches what `stream-manager.bridge.ts:509` already expects.

Tests

  • 9/9 existing tests updated to mock `get_stream_writer` instead and assert on `writer.call_args`
  • 1 new test for graceful behavior outside a stream context (writer raises RuntimeError; handler swallows)
  • 10/10 passing locally

Live smoke

`a2uiPartialEvents: 758` across a 2.5 MB SSE stream for a dashboard prompt. `agent.customEvents()` now populates with all 758 events.

Known follow-up

A frontend partial-args-bridge bug remains: early surfaceUpdate envelopes dispatch with incomplete `components` arrays (ids not yet streamed), so `pickRoot` returns null and synthesis is skipped. Subsequent pushes then skip the surfaceUpdate envelope because `dispatchedCount` already advanced. Live `liveSurfaceStore` stays empty even though events flow correctly. Tracking separately — out of scope for this PR.

Test plan

  • `pytest tests/test_a2ui_partial_handler.py tests/test_streaming_smoke.py` — 10/10
  • Live wire-level verification: 758 `a2ui-partial` events / 2.5 MB SSE stream
  • CI green

PR #266 fixed the on_llm_new_token signature, after which live diagnostics
confirmed the handler ran per token and adispatch_custom_event completed
successfully — yet the SSE stream still carried zero a2ui-partial events.

Root cause: adispatch_custom_event (langchain_core) and stream_mode=
'custom' (LangGraph) are different layers. langchain_core dispatches
visible via stream_mode='events'; LangGraph's 'custom' channel is fed
by get_stream_writer() returned from langgraph.config. The transport
already requests stream_mode='custom', but the handler was writing to
the wrong sink.

Fix: replace adispatch_custom_event with get_stream_writer(). The writer
is contextvar-scoped to the currently-executing LangGraph node and is
inherited by nested callbacks, so the handler can call it from inside
the LLM's callback chain. The payload shape is {name, data} so the
existing transport-side parser (stream-manager.bridge.ts:509) extracts
name + data correctly.

Tests updated to mock get_stream_writer (instead of adispatch_custom_event)
and assert on writer.call_args. Adds a new test asserting graceful
behavior when invoked outside a stream context (writer raises
RuntimeError; handler swallows).

Live smoke now confirms 758 a2ui-partial events on the wire across a
2.5MB SSE stream for a dashboard prompt. The bridge consumes all events
into agent.customEvents() and forwards them to the partial-args bridge.

Note: a follow-up frontend issue remains in the partial-args bridge's
incremental dispatch logic — early surfaceUpdate envelopes dispatch with
incomplete components arrays (no ids yet), preventing beginRendering
synthesis, and the dispatchedCount counter then skips re-attempt.
Tracking separately.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 12, 2026 11:18pm

Request Review

@blove blove merged commit b590d78 into main May 12, 2026
14 checks passed
@blove blove deleted the claude/genui-streaming-use-stream-writer branch May 12, 2026 23:19
blove added a commit that referenced this pull request May 12, 2026
…rk (#271)

Brings the canonical smoke checklist current with 29 PRs that landed
between Phase 7 (#239) and today without checklist updates. Specifically:

Updated sections:
- chat-debug devtools — replaced bottom-drawer model with floating
  launcher + status pill + switch (PRs #249, #251)
- Control palette — palette v2 (status pill, shadcn-styled panel, PR #244)
- Generative UI / A2UI surfaces — single-bubble invariant (PR #255),
  parent-emits-envelopes architecture (PR #259), wrapped-content +
  tool_calls coexistence (PR #255), envelope reorder
- Server-side wire format — tool_calls preserved on the final AI
- Replaced 'Multi-thread' section with 'Sidenav (thread management)'
  reflecting the permanent semantic <nav> + Active/Archived sections
  (PR #253) and removing the old palette-toggled drawer model

Added sections:
- Cmd+K history search — palette open/search/select/close, archived
  result subtitle, keyboard navigation (PR #253)
- Per-row thread actions — kebab menu order per state (active, pinned,
  archived), rename + pin/unpin + archive/unarchive + delete flows
  (PRs #258, #260, #267)
- Thread titles — first-user-message derivation, idempotent writes,
  manual rename precedence (PR #242)
- Progressive A2UI streaming — per-component fallback transition
  observable during streaming window (PRs #252, #261, #262, #268, #269)
- Inline checkpoint markers — render between messages during multi-step
  runs (PR #243)
- Responsive sidenav — viewport breakpoints, auto-collapse behavior (PR #240)

Total: ~58 new check items across 6 new sections, plus rewrites to 5
existing sections. Original 333-line checklist → 391 lines / 237 check
items.
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