fix(chat+langgraph): streaming complex-content + GFM markdown + model picker (chat 0.0.18, langgraph 0.0.10)#191
Merged
Conversation
…ubbles
OpenAI's gpt-5/o-series Responses API streams `BaseMessage.content` as
arrays of typed blocks (`[{type:'text',text:'…',index:n}, …]`) rather
than accumulated strings, and emits two parallel views of the same
assistant message: per-event `AIMessageChunk`s on `messages-tuple` and
the canonical `ai` on `values`-sync. The bridge wasn't equipped for
either — chunks JSON-dumped into the bubble and the two paths each
spawned their own bubble that never collapsed.
Bridge fixes (`stream-manager.bridge.ts`):
- `extractText` walks complex-content arrays and pulls visible text
blocks (`text` / `output_text`), skipping reasoning / tool_use /
image blocks.
- `accumulateContent` merges incoming-chunk content into the prior
slot's accumulated text. Handles the three cases: incoming is a
strict superset (final-id swap), existing is a strict superset
(chunk arrives after canonical), or pure delta append. Always
returns string so downstream `findContentMatch` can prefix-compare
cleanly without `JSON.stringify`-ing the array.
- `normalizeMessageType` collapses `AIMessage` / `AIMessageChunk` /
`ai` / `assistant` to `ai` so `findContentMatch` and
`sameRoleAndContent` correctly match across the values-sync and
messages-tuple paths.
- `mergeMessages` gains an AIMessageChunk fallback: when an
AIMessageChunk arrives without an id-match or content-prefix match,
accumulate into the trailing AI message. The OpenAI Responses API
emits per-chunk *event* ids, not message ids, so consecutive chunks
would otherwise each create a fresh bubble.
- Empty-content AI placeholders are dropped from `state.messages`
before the values-sync merge — keeping them creates a phantom slot
that competes with the chunk-streamed AIMessageChunk.
- `collapseAdjacentAi` post-pass collapses adjacent AI messages where
one's text is a prefix/equal of the other, keeping the older slot's
id for stable track-by-id.
Also dropped an obsolete hand-rolled rawMessages throttle in
`agent.fn.ts` — `messages$` already emits at the bridge boundary and
extra signal-side throttling collapsed visible token streaming.
Bumps @ngaf/langgraph 0.0.9 → 0.0.10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k styles
Two related rendering bugs converged into a chat that showed raw JSON
arrays and then, after fixing that, plain unstyled paragraphs with no
heading sizes, bullets, or table borders.
`messageContent` shared util:
- Was JSON-stringifying complex-content arrays, dumping
`[{"type":"text",…}]` into the chat bubble for OpenAI gpt-5 / o-series
output. Now extracts visible `text` / `output_text` blocks the same
way the langgraph adapter does and joins them; everything else
(reasoning, tool_use, images) is skipped.
`chat-streaming-md` component:
- Switched to `ViewEncapsulation.None`. The component renders markdown
by assigning sanitized HTML to `innerHTML`, so the resulting `<ul>`,
`<p>`, `<table>`, etc. nodes never carry the `_ngcontent-…`
attribute that emulated encapsulation requires. Without this the
exported `CHAT_MARKDOWN_STYLES` were silently skipped for every
selector below `:host`.
- Wired `CHAT_MARKDOWN_STYLES` into the component (it was exported but
never applied anywhere).
`CHAT_MARKDOWN_STYLES`:
- Re-scoped from `:host` to `chat-streaming-md` element selectors so
the rules stay locally meaningful under `ViewEncapsulation.None`.
- Expanded coverage to the full CommonMark + GFM surface: heading
hierarchy (h1–h6 with size scale), `strong` / `b`, `em` / `i`,
`del` / `s`, `mark`, `sub` / `sup`, `a` (with hover), bullet /
ordered / nested lists with visible `disc` / `circle` / `decimal`
markers, GFM task-list checkboxes, inline `code` / fenced `pre`,
`blockquote`, `hr`, GFM `table` (bordered, header background), and
`img` (max-width).
`marked` options:
- Enabled `gfm: true, breaks: true` so single newlines render as
`<br>` (matching common chat UX) and tables / strikethrough / task
lists / autolinks are honored.
Bumps @ngaf/chat 0.0.17 → 0.0.18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…estions as pills Two UX improvements driven by smoke-testing the chat-select primitive in real apps. `<chat>` composition: - New `[modelOptions]`, `[(selectedModel)]`, `[modelPickerPlaceholder]` inputs that, when populated, render a `<chat-select chatInputModelSelect>` inside the chat-input pill on both the welcome screen and the conversation footer. Consumers no longer have to project the slot themselves for the common "model picker" use case — they just pass options + a model signal. - Slot projection still works in conversation mode for any consumer that needs custom chat-input children (an inner `ngProjectAs` bridges the outer `[chatInputModelSelect]` content through). Welcome suggestions: - Restyled from full-width stacked rows separated by dividers to centered floating pills (`border-radius: 9999px`, surface background, equal gap). Matches the chat-input pill aesthetic. - Container becomes `flex-wrap` + `justify-content: center` + `gap: 8px` so suggestions reflow naturally on narrow viewports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…5-mini Default model in every demo graph and prose example is now gpt-5-mini. Reasoning models (gpt-5/o-series) stream visibly out of the box at `reasoning.effort='minimal'`, which the langgraph adapter sets by default — no developer-facing config needed. Older non-reasoning gpt-4o-mini references in graphs and docs were stale. No code path changed; pure model-name swap across cockpit examples and docs/superpowers plan / spec files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Design spec for the next chat phase: surface model reasoning content as a first-class collapsible "Thinking… / Thought for Ns" pill above the assistant response, and turn tool-call rendering into a first- class extension surface via a chatToolCallTemplate directive while keeping a polished default that auto-collapses completed cards and groups sequential same-name calls. Lands one new primitive (<chat-reasoning>), one new directive (chatToolCallTemplate, including a "*" wildcard catch-all), augments two existing primitives (<chat-tool-calls>, <chat-tool-call-card>), and adds two new optional fields (Message.reasoning, Message.reasoningDurationMs) populated by both adapters from provider-agnostic sources: LangGraph complex-content reasoning blocks and AG-UI REASONING_MESSAGE_* events. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prior assertion checked that JSON-stringified output contained the
literal string 'text' (the JSON key from {type:'text',...}). After the
0.0.18 fix to extract visible text instead of dumping JSON, the
assertion is wrong by construction. Replaced with two assertions:
single-block text extraction, and multi-block concatenation that skips
reasoning blocks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eleven phases, ~70 tasks. TDD throughout for new primitives + the reasoning conformance fixture. Each task is self-contained with full context, exact paths, complete code, and explicit verification commands. Subagent-friendly per superpowers:subagent-driven-development. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacks on top of #189 (chat 0.0.17). Four commits, two version bumps.
fix(langgraph)— stream complex-content correctly + dedupe assistant bubblesOpenAI gpt-5/o-series Responses API streams typed content blocks (
[{type:'text',text:'…',index}]) and emits two parallel views of the same assistant message: per-eventAIMessageChunks onmessages-tupleand the canonicalaionvalues-sync. The bridge wasn't equipped for either — chunks JSON-dumped into the bubble and the two paths each spawned their own bubble that never collapsed.Fixes in
stream-manager.bridge.ts:extractTextwalks complex-content arrays, pullstext/output_textblocksaccumulateContentmerges chunk content into the prior slot (handles superset / prefix / pure-delta cases); always returns string for stable downstream prefix-matchingnormalizeMessageTypecollapsesAIMessage/AIMessageChunk/ai/assistanttoaiso dedupe matches across pathsmergeMessages: when anAIMessageChunkarrives without id-match or content-prefix-match, accumulate into the trailing AI message (Responses API emits per-event ids, not message ids)state.messagesbefore the values-sync merge — keeping them creates a phantom slot that competes with the chunk streamcollapseAdjacentAipost-pass merges adjacent AI messages where one's text is a prefix/equal of the otherAlso drops an obsolete hand-rolled
rawMessagesthrottle inagent.fn.ts. Bumps@ngaf/langgraph0.0.9 → 0.0.10.fix(chat)— render markdown with full GFM coverage + visible CommonMark stylesTwo related rendering bugs converged: chat showed raw JSON arrays, then after fixing that, plain unstyled paragraphs with no heading sizes, bullets, or table borders.
messageContentshared util: was JSON-stringifying complex-content arrays. Now extractstext/output_textblocks the same way the langgraph adapter does.chat-streaming-md: switched toViewEncapsulation.Noneand wired inCHAT_MARKDOWN_STYLES. The component renders sanitized HTML viainnerHTML, so emulated encapsulation was silently skipping every selector below:host.CHAT_MARKDOWN_STYLES: re-scoped from:hosttochat-streaming-mdelement selectors, expanded to full CommonMark + GFM (h1–h6 hierarchy,strong/em/del/mark/sub/sup, links with hover, bullet / ordered / nested lists with visible markers, GFM task-list checkboxes, inlinecode/ fencedpre,blockquote,hr, GFMtable(bordered + header bg),img(max-width)).markedenabled withgfm: true, breaks: true.Bumps
@ngaf/chat0.0.17 → 0.0.18.feat(chat)— first-class model picker on<chat>+ welcome suggestions as pills[modelOptions]+[(selectedModel)]+[modelPickerPlaceholder]inputs project a<chat-select chatInputModelSelect>into the input pill on both welcome and conversation views — no manual slot wiring for the common case.border-radius: 9999px, surface bg,flex-wrap: wrap, equal gap). Matches the input pill aesthetic.chore— gpt-4o-mini → gpt-5-mini sweepPure model-name swap across
cockpit/**demo graphs/docs anddocs/superpowers/{plans,specs}/**. Reasoning models stream visibly with the langgraph adapter'sreasoning.effort='minimal'default.Test plan
🤖 Generated with Claude Code