Copies the hitl-in-app demo verbatim from langgraph-python (north-star):
page.tsx, approval-dialog.tsx, suggestions.ts, tickets-panel.tsx. Updates
the ADK agent instruction to match the LP support-operations prompt and
the {approved, reason} tool-return shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy page.tsx and time-picker-card.tsx verbatim from the langgraph-python
north-star demo. Delete README.md (not present upstream). Frontend now uses
agent="hitl-in-chat" / agentId="hitl-in-chat" exactly as LP — registry and
route.ts currently key on "hitl_in_chat", which will need a follow-up
rename to wire end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the inline-styled brand-palette version with a 1:1 verbatim copy of
the langgraph-python north-star demo: ShadCN-flavoured Card/Badge/Button
primitives under _components/, neutral zinc chart palette, and the
chat.tsx + suggestions.ts split out of page.tsx.
The ADK agent already emits the same a2ui_operations container shape
(via build_a2ui_operations_from_tool_call in agents/main.py) so no
backend changes are needed; the frontend swap is sufficient.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy LP gen-ui-agent demo verbatim: page.tsx now uses InlineAgentStateCard
in messageView.children (subscribing to live state via useAgent v2 +
UseAgentUpdate.OnStateChanged), plus the supporting
InlineAgentStateCard.tsx, message-list-with-state.tsx, suggestions.ts, and
README.md.
Update the ADK gen_ui_agent to emit the same Step shape the LP frontend
reads — {id, title, status} with the three-state lifecycle pending ->
in_progress -> completed, driven by a six-call sequence so the card
animates through every step. Without this, the card would render empty
titles and never show the in_progress marker.
Delete the orphan agent.py stub from the demo dir — the real backend
agent lives in src/agents/gen_ui_agent.py and is wired through the
shared registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verbatim copy of the langgraph-python tool-rendering demo frontend to
google-adk: page.tsx + weather-card + flight-list-card + stock-card +
d20-card + custom-catchall-renderer + suggestions + README. Also adds
the _shared/parse-json-result helper required by page.tsx. Removes the
stale agent.py stub and render-flight-tool.snippet.tsx that don't exist
in the LP north-star.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the north-star (langgraph-python) mcp-apps demo verbatim: split the
single-file page.tsx into page.tsx + chat.tsx + suggestions.ts, and switch
the agent identifier from "mcp_apps" to "mcp-apps" to match LP exactly.
Update copilotkit-mcp-apps/route.ts to register the HttpAgent under the
"mcp-apps" key so the LP-verbatim frontend resolves. Delete the demo-local
README.md (no LP equivalent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy LP's tool-rendering-reasoning-chain frontend verbatim: page.tsx with
useRenderTool registrations for get_weather + search_flights and a
useDefaultRenderTool catchall, plus ReasoningBlock, WeatherCard,
FlightListCard, and CustomCatchallRenderer components. Keeps the LP
agent/agentId values ("tool-rendering-reasoning-chain") as required by
the parity north-star.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bring shared-state-streaming demo frontend to 1:1 parity with the
langgraph-python north-star: split page.tsx into demo-layout.tsx +
suggestions.ts modules, swap CopilotChat-in-aside for
CopilotSidebar+DemoLayout, and adopt LP's DocumentView styling +
testids (document-view, document-live-badge, document-char-count,
document-content).
The ADK agent (shared_state_streaming_agent.py) is already wired with
streaming_function_call_arguments=True on the ADKAgent middleware and
PredictStateMapping(stream_tool_call=True), so per-token (or chunk-level
fallback on Gemini Studio) state-delta streaming into state["document"]
works end-to-end. The 2026-05-07 not_supported_features bucket was
conservative — the wiring is complete and the LIVE badge / growing
document UX functions even on the chunk-level fallback path.
README adjusted from LangGraph's StateStreamingMiddleware to ADK's
PredictStateMapping + streaming_function_call_arguments wording.
Manifest change needed (not in this commit): remove
shared-state-streaming from not_supported_features and add to features.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy the readonly-state-agent-context demo frontend verbatim from
langgraph-python (the north-star) into google-adk. The ADK demo
previously had a different layout (sidebar + CopilotChat with 3 simple
fields); replace it with the LP layout (identity card + activity card +
published-context JSON viewer + CopilotPopup, all on a neutral-50
background with the indigo accent system).
Adds local _components/ shadcn primitives (Badge, Card, Checkbox, Input,
Label, Select), demo-layout.tsx, and suggestions.ts. The page.tsx is
identical to LP — agent="readonly-state-agent-context" and
agentId="readonly-state-agent-context" (dash form) are kept verbatim per
the parity rules; the orchestrator will rename the registry key so the
backend matches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verbatim port of langgraph-python's agent-config demo frontend:
page.tsx, config-card.tsx, config-context-relay.tsx, config-types.ts,
demo-layout.tsx, use-agent-config.ts. Keeps agent="agent-config-demo"
and runtimeUrl="/api/copilotkit-agent-config" as in LP.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy the auth demo verbatim from langgraph-python (the parity north-star).
The demo now uses an unauthenticated-first UX with a dedicated SignInCard
landing page; <CopilotKit> mounts only after the user signs in at least
once, sidestepping the transport 401 that would crash <CopilotChat /> on
its initial /info handshake. Once mounted, it persists across the
sign-out -> sign-in cycle so the post-sign-out state actually demonstrates
the runtime rejecting unauthenticated requests in the chat surface.
Workspace plumbing required to make the verbatim port runnable:
- Add shadcn primitives (button, card) and `cn` util at
src/components/ui/* + src/lib/utils.ts.
- Add class-variance-authority, clsx, radix-ui, tailwind-merge to
package.json.
- Extend globals.css with Tailwind v4 @theme tokens that map onto the
shadcn CSS variables (background/foreground/card/muted/border/etc.) so
utilities like `bg-muted` and `text-muted-foreground` actually generate
styles.
The ADK auth API route already had the onRequest bearer-token gate
mirroring LP's pattern; it points at the `_simple_chat`-backed `/auth`
agent path, which is correct per the auth-at-route-level model. No
backend (registry / agent) changes required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verbatim copy of the LP beautiful-chat frontend — page.tsx, home-page.tsx,
showcase.json, declarative-generative-ui/, components/ (example-layout,
example-canvas, generative-ui, ui, tool-rendering), hooks/, lib/. The demo
now uses ThemeProvider + CopilotKit with a2ui catalog + openGenerativeUI,
ExampleLayout/Canvas with todos, generative UI chart components, and the
full suggestion-pill set.
Add the dedicated ADK runtime route at /api/copilotkit-beautiful-chat
modelled after the LP route shape but adapted for ADK (HttpAgent +
AGENT_URL pattern, mirroring declarative-gen-ui and mcp-apps routes).
Combined runtime: openGenerativeUI + a2ui.injectA2UITool:false + mcpApps
to support all three flagship surfaces simultaneously, matching LP.
Add missing npm deps to package.json: @radix-ui/react-checkbox,
@radix-ui/react-separator, class-variance-authority, clsx, lucide-react,
tailwind-merge — required by the verbatim-copied shadcn primitives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align google-adk's /demos/voice with the langgraph-python north-star:
- page.tsx now mounts <CopilotKit runtimeUrl="/api/copilotkit-voice"
agent="voice-demo" useSingleEndpoint={false} enableInspector={false}>
around a dedicated <VoiceChat /> component (split out of page.tsx).
- voice-chat.tsx (new) hosts the textarea-injection handler and renders
<CopilotChat agentId="voice-demo" /> alongside the sample-audio button.
- sample-audio-button.tsx is the single-button "Try a sample audio" variant
with the mic emoji and tooltip-style preview — the surrounding "Sample:"
wrapper from the old ADK version is gone.
- Drops voice-frontend.snippet.tsx and voice-runtime.snippet.ts (not in LP).
Agent untouched: src/agents/shared_chat._simple_chat already mirrors LP's
main.py simple chat shell. The existing route at
src/app/api/copilotkit-voice/[[...slug]]/route.ts already maps
"voice-demo" → HttpAgent(AGENT_URL + "/voice") which lines up with the
registry key "voice".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy the full headless-complete demo verbatim from langgraph-python (the
parity north-star). The demo now exposes the full headless surface
CopilotKit provides: useRenderTool / useDefaultRenderTool, useComponent,
useConfigureSuggestions, useAttachments, plus useRenderToolCall and
useRenderActivityMessage rendered inside a hand-rolled chat shell built on
useAgent + useCopilotKit.
Workspace plumbing required to make the verbatim port runnable:
- Add shadcn primitives (avatar, badge, button, card, scroll-area,
separator, textarea) and `cn` util at src/components/ui/* + src/lib/utils.ts.
- Add lucide-react, radix-ui, react-markdown, remark-gfm, clsx,
tailwind-merge, class-variance-authority to package.json.
- Extend globals.css with Tailwind v4 @theme tokens that map onto the
shadcn CSS variables (background/foreground/card/muted/border/etc.) so
utilities like `bg-background` actually generate styles.
- Register a `headless-complete` HttpAgent on the existing MCP Apps
runtime route so the LP frontend's `agent="headless-complete"` resolves
to the ADK backend path `/headless_complete` (mapped to _simple_chat in
registry.py).
The ADK `_simple_chat` agent is left unchanged — it already covers the
behaviour the demo exercises (frontend tools, MCP Apps tool calls).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs the same Depot Docker build as the post-merge pipeline but with
push: false, catching Dockerfile-specific failures (missing deps,
broken layers) before they land on main.
Reduce google-adk's agentic-chat to the minimum-viable CopilotChat surface
matching langgraph-python: vanilla <CopilotChat /> + three starter
suggestion pills. Strip useFrontendTool (change_background), useRenderTool
(get_weather — was silently failing, no backend tool registered), and
useAgentContext (Bob's name). These concepts already have dedicated demos
(frontend-tools, tool-rendering, readonly-state-agent-context).
Also: make GOOGLE_GEMINI_BASE_URL overridable via .env so the same
container can flip between aimock (deterministic D5 probes) and real
Gemini (manual verification) without editing the compose file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
- New `<DemoSource>` client component renders bundled demo source files
directly in the InlineDemo Code tab, replacing the deprecated
`feature-viewer.copilotkit.ai` iframe
- Bundler emits a new `highlightOrder?: number` field on each
`DemoFile`, so file tabs render in the manifest's `highlight:[]` order
(the author's intended teaching sequence) rather than the bundler's
page-first alphabetical sort
- Deletes the feature-viewer slug/demo-ID translation layer
(`FEATURE_VIEWER_SLUG_OVERRIDES`, `FEATURE_VIEWER_DEMOS`,
`getFeatureViewerSlug`, `getFeatureViewerDemoId`)
## Why
- `feature-viewer.copilotkit.ai` is being decoupled; only 6 of ~30 demos
shell-docs references were ever supported there, so the Code tab was
hidden or 404'd on the majority of (framework × demo) combinations
- `<DemoSource>` reads from the same `demo-content.json` bundle the
`<Snippet>` system already uses, so coverage expands to all bundled
demos with routes (~700 entries) by construction — no per-demo
translation map to maintain
- Removes one external dependency from the InlineDemo render path
## Implementation notes
- `<DemoSource>` is a single client component (~226 lines);
`highlight.js` runs in the browser. A server-pre-render split that moves
hljs off the client bundle is a possible follow-up if bundle size
becomes a concern.
- Defaults to `onlyHighlighted={true}` — shows just the manifest
`highlight:[]` files (typically 3–6 per demo). Pass
`onlyHighlighted={false}` to render every file in the demo folder.
- Falls back to all files if no files are flagged `highlighted`
(resilience against manifests without a `highlight:[]` array).
## Test plan
- [ ] Pages with `<InlineDemo>` for a previously-supported demo render
the Code tab via the new component (e.g.
`/langgraph-python/agentic-chat-ui`,
`/langgraph-python/human-in-the-loop`)
- [ ] Pages with `<InlineDemo>` for a previously-unsupported demo now
render the Code tab (e.g. `/langgraph-python/frontend-tools`,
`/langgraph-python/voice`, `/langgraph-python/agent-config`)
- [ ] Pages on integrations that had no feature-viewer entry now render
the Code tab (e.g. `/built-in-agent/*`, `/google-adk/*`, `/ms-agent-*`)
- [ ] File tabs render in manifest `highlight:[]` order (spot-check
`frontend-tools`: agent file should appear before page.tsx)
- [ ] Copy button copies the active file contents
- [ ] Visual style matches existing `<Snippet>` blocks on the same page
- [ ] `nx run shell-docs:build` passes
The InlineDemo Code tab previously embedded feature-viewer.copilotkit.ai
in an iframe. Feature-viewer only ships six canonical demos for a
limited set of frameworks, so every other (framework x demo) pair —
including the dozen-plus newer demos like frontend-tools, voice,
subagents, gen-ui-interrupt — rendered a 404 or had its Code tab
suppressed entirely.
Add a client-side <DemoSource> component that reads the same
demo-content.json bundle <Snippet> already consumes, scoped to one
(integration, demo) cell. By default it shows only files flagged in the
manifest's `highlight:` array, sorted by the new `highlightOrder` field
so tabs render in author-defined order. Falls back to all bundled files
when nothing is flagged. Rendering matches <Snippet>'s look (same hljs
classes, CopyButton, border / type scale) for visual continuity.
Wire <DemoSource> into the InlineDemo Code tab and remove the
feature-viewer URL construction. The base import of getDocsFolder is
dropped from mdx-registry.tsx since it was only used for the iframe
URL; getDocsFolder remains in registry.ts for the framework routing
layer that still depends on it.
Adds an optional `highlightOrder` field (0-based index into the
manifest's `highlight:` array) on each bundled DemoFile that is flagged
`highlighted: true`. Consumers like shell-docs's new <DemoSource>
component use this to render highlighted files in the manifest author's
preferred order rather than the bundler's alphabetical-with-page-first
fallback. Files not in `highlight:` carry no `highlightOrder` and keep
the existing sort behavior.
Additive field; no migration needed.
## Summary
Ten commits porting `docs.copilotkit.ai`'s visual baseline + page
architecture onto shell-docs ahead of the May 12 cutover.
This is a **visual-replica port**, not an IA change — the docs
information architecture stays as shipped (sidebar groupings, JTBD
section names, BIA-as-default behavior). What changes is the visual
layer: colors, typography, sidebar/navbar/TOC chrome, page layout
architecture, banner, content-column geometry, search modal, dark mode,
and docs page chrome polish.
## Commits
1. **Visual baseline** — color tokens (accent, glass-background, bg),
typography (Plus Jakarta Sans + system mono), callouts (white card +
colored left strip + lucide icons), tables (row-bottom borders),
code-block chrome (`rounded-xl` + `shadow-sm`), `.docs-content-wrapper`
rule (white panel + left-edge fade).
2. **Page layout architecture** — replicates canonical's fixed-height
body + internal scroll on `.docs-content-wrapper`. Banner + navbar +
sidebar are naturally at the top/left of body (no sticky positioning).
TOC moves inside the content wrapper. Route shells normalized to `h-full
w-full` with explicit scroll wrappers.
3. **Two-piece navbar** — slanted SVG separator between left brand panel
and right utility cluster, glass-panel chrome, three-zone search trigger
(icon + label + ⌘K), per-link underline cross-fade animation, brand
assets (kite mark + slanted borders + theme icons).
4. **Dismissable top promo banner** — `<Banners />` component with
localStorage TTL, lucide rocket icon, `id` namespacing.
5. **Right-rail TOC** — direct port of fumadocs-ui's clerk pattern:
persistent gray vertical guides at depth-specific offsets + diagonal SVG
connectors at H2↔H3 transitions + a violet thumb that paints only along
the line path via SVG mask. Active text turns violet, inactive at 60%
opacity.
6. **Sidebar pill + content alignment** — adds `pr-1` to the sidebar's
inner scroll container so the active-link pill stops 4px short of the
scrollbar (matches canonical). Replaces the content column's `px-8 py-6
xl:px-16 xl:py-12` + non-centered `max-w-[900px]` with canonical's exact
`px-4 py-6 md:px-6 md:pt-8 xl:px-8 xl:pt-14` outside, `max-w-[900px]
mx-auto` inside, so the column centers between sidebar and TOC and the
h1 lands at the same x as `docs.copilotkit.ai` at 1440.
7. **Search modal portal** — the modal renders inside SearchTrigger,
which lives in the navbar's right cluster. `backdrop-filter` creates a
containing block for fixed-position descendants, which was clamping the
modal's `fixed inset-0` overlay to the cluster (~505x70px) instead of
the viewport. Wraps SearchModalWrapper in `createPortal` mounted on
`document.body` so the overlay covers the page.
8. **Dark mode parity** — adds `.dark` token block to `globals.css`
mirroring canonical for every var the wave3 chrome consumes;
`@custom-variant dark (&:is(.dark *))` so `dark:` Tailwind utilities
react to the `.dark` class instead of `prefers-color-scheme`; inline
`beforeInteractive` script that reads `localStorage.theme` (falling back
to `prefers-color-scheme`) and applies the class before first paint with
`suppressHydrationWarning` on `<html>` so Next.js doesn't revert it;
themed thin scrollbar on `.docs-content-wrapper` and the sidebar's inner
scroll container; rounded hover surface on the navbar's
GitHub/Discord/theme icon buttons.
9. **TOC scrollspy at scroll bottom** — replaces IntersectionObserver
with a scroll listener on `.docs-content-wrapper` that walks the heading
list and forces the final heading active when the scroll container is at
`scrollMax`. The previous observer never fired for the last heading once
it had scrolled past the rootMargin band.
10. **Docs page chrome polish** — tightens the page header → body gap
from `mb-14` (responsive) to a flat `mb-8` matching canonical; section
header banner sized at 15px with same Plus Jakarta default-weight
uppercase + tracking as canonical's separator, sized up; `--border`
(instead of `--border-dim`) on the section divider rule so it survives
dark; active-link pill gets a `--bg-hover` surface + 1px white/10 ring
in dark for contrast; idle pages get `dark:hover:bg-white/5`; nested
section separators (depth > 0) demote to 11px `--text-faint` labels with
no divider rule (no more shouting at the same hierarchy as the parent
banner); root `/docs` overview moves onto the same SidebarNav +
`.docs-content-wrapper` + `max-w-[900px] mx-auto` shell the per-doc
routes use; rename the "Give Your App Agent Powers" main-meta section to
"Adding Agent Powers".
## Verification
- `npm run build` clean.
- 1440x900 Playwright at scroll 0 vs scrollMax: nav
`getBoundingClientRect()` delta = `{top: 0, left: 0, width: 0}` — zero
drift.
- 1440x900 Playwright vs `docs.copilotkit.ai`: aside, wrapper, and h1.x
coordinates match canonical exactly; active-link pill has the same gap
to the scroll gutter as canonical.
- Search modal opens as a full-viewport overlay (1440x900 backdrop,
centered card) instead of a clipped strip under the trigger.
- Dark mode round-trips light → click toggle → `html.dark` +
`localStorage.theme="dark"` → click again → light. Sidebar, navbar
(slanted-dark borders + theme moon icon), banner, content panel,
callouts, code, tables, TOC, scrollbar, and search modal all paint
correctly in dark; no light-flash on dark-preferring first loads.
- TOC last heading activates at scroll bottom on all docs pages.
## Out of scope
- IA changes (sidebar groupings, navbar destinations, content) — content
stays as shipped.
## Test plan
- [ ] Visual review at 1440x900 against `docs.copilotkit.ai`: sidebar
pinning, navbar pinning, TOC outline + sliding violet, content-panel
left-edge gradient, content column centering, sidebar section header
treatment
- [ ] Active-link pill in the docs sidebar has visible gap to the
scrollbar in both light and dark
- [ ] Cmd/Ctrl+K opens the search modal as a full-viewport overlay;
click backdrop or Escape closes
- [ ] Toggle theme button switches light↔dark, persists across reload,
no light-flash on first load
- [ ] Scroll a long page (e.g. `/built-in-agent/concepts/architecture`)
— confirm last TOC heading activates at the bottom
- [ ] Mobile (< 1280px) — TOC hides, sidebar collapses to mobile menu
## Summary
- **Runtime bug fix:** `LangGraphAgent.run` now strips `role:
"reasoning"` messages from `input.messages` before forwarding to
`@ag-ui/langgraph`'s message converter. Without this, the second turn of
any thread containing reasoning summaries (OpenAI Responses API +
`reasoning={summary:"detailed"}`) crashes with `"message role is not
supported."` before the model is ever called.
- **Showcase polish:** the `tool-rendering-reasoning-chain` demo
previously promised chained tool calls in its pill titles but delivered
single tools — fixed by rewording the pill prompts to pre-disclose the
chain, replacing the soft system prompt with concrete per-pill chain
examples (matching the langgraph-typescript `tool-rendering` agent
pattern), and adding 9 chained aimock fixtures.
## What was broken
In `/demos/tool-rendering-reasoning-chain`, clicking a second pill in
the same thread produced:
```
Error: message role is not supported.
Code: agent_run_error_event
runtimeErrorCode: INCOMPLETE_STREAM
```
Root cause: `@ag-ui/langgraph` ships a message converter that throws on
any role outside `{user, assistant, system, tool}`. Reasoning-stream
agents emit AG-UI `role:"reasoning"` messages during a turn; the AG-UI
client persists them in the thread and replays them on the next request;
the converter crashes before the model is called.
Separately, even when a single pill click succeeded, the agent was
emitting only ONE tool call — the pill titles ("Weather + flights to
Tokyo", "Compare two stocks", "Chain of dice rolls", "Flights +
destination weather") all promised chains but neither the system prompt
nor the aimock fixtures supported them.
## The fix
### `packages/runtime` (commit 1)
Single 4-line filter in `LangGraphAgent.run`. Only the *inbound* message
list is filtered — the outbound event stream still carries reasoning
summaries to the client, so the `<ReasoningBlock>` slot continues to
render on the active turn. See the in-file comment for the AG-UI
converter reference.
### `showcase/integrations/langgraph-python` (commit 2)
- **System prompt** rewritten with per-pill chain instructions (mirrors
the langgraph-typescript `tool-rendering.ts` pattern).
- **Pill messages** reworded to pre-disclose the chain so the model
commits to a follow-up call:
- `"Compare AAPL and MSFT stocks for me."`
- `"Roll a 20-sided die for me and compare it to a smaller one."`
- `"Find flights from SFO to JFK and show me the weather there."`
- Dropped the redundant "Weather + flights to Tokyo" pill (it was the
SFO/JFK chain in reverse).
- **9 aimock fixtures** (3 per pill: `final-content → second-leg →
first-leg`, ordered by `toolCallId` specificity). Each fixture is scoped
by a langgraph-python-UNIQUE `userMessage` substring tail — `"Compare
AAPL and MSFT stocks"`, `"compare it to a smaller one"`, `"show me the
weather there"` — none of which appear in any other integration's pill
prompts. So the fixtures do not cross-contaminate the other 14+
reasoning-chain demos sharing the Railway `showcase-aimock` service.
- **Harness probe** collapsed from two single-tool turns to one chained
turn that asserts BOTH per-tool renderers (`FlightListCard` +
`WeatherCard`) mount — same coverage as before at half the wall-clock,
AND it actually exercises the chained-tool path.
## Why not gate on `toolName: "roll_dice"`?
Initial attempt used `toolName: "roll_dice"` as the differentiator
between this demo and the basic `tool-rendering` demo. Code review
caught that aimock's `toolName` matcher is a tool-LIST gate (checks the
request's available-tools list), not a tool-CALL gate, and most
reasoning-chain agents across the fleet (agno, built-in-agent,
claude-sdk-python, ms-agent-python, pydantic-ai, langgraph-fastapi, …)
also register `roll_dice`. The gate would NOT have isolated this demo on
the shared Railway `showcase-aimock`. Scoping by unique prompt-tail
substring is both cheaper and more reliable.
## Out of scope (deliberate)
- The other 17 integrations' `tool-rendering-reasoning-chain/page.tsx`
still ship the older pill prompts and the soft system prompt. Same fix
applies; not bundled here to keep the diff reviewable.
- Pre-existing typecheck errors in `packages/runtime/src/index.ts`
(missing `@copilotkit/license-verifier` exports) and decorator errors in
`src/graphql/inputs/message.input.ts` exist on `main` and are not
touched here.
## Test plan
- [x] Local manual verification against real OpenAI (gpt-5.4, Responses
API) — multi-pill thread no longer crashes; each pill produces 2
sequential tool calls and a final summary.
- [x] JSON validation: both `d5-all.json` and the harness fixture file
parse.
- [x] Typecheck on touched files clean (`packages/runtime`,
`showcase/harness`).
- [x] Lefthook pre-commit gates green on both commits.
- [ ] CI runs the updated harness probe against the canonical Docker
stack.
Three layers of regression guards for the runtime reasoning-role filter and
the chained demo behavior:
1. Runtime unit test — packages/runtime/.../run-message-filtering.test.ts:
- Verifies `LangGraphAgent.run` strips `role:"reasoning"` from
`input.messages` before delegating to super.run.
- Verifies user/assistant/system/tool messages pass through in order.
- Verifies empty + missing messages arrays are tolerated.
- Verifies pre-existing forwardedProps.streamSubgraphs default + override
behavior is preserved.
- 6/6 tests pass against the runtime package's vitest config.
2. D5 harness probe — showcase/harness/.../d5-tool-rendering-reasoning-chain.ts:
- Expanded from one chained turn (flights→weather) to all three chained
pills in a single thread (stocks AAPL→MSFT, dice d20→d6, flights→weather).
- This is the canonical multi-pill regression at the harness layer:
without the runtime reasoning-role filter, the second pill would crash
before the model was called.
- Each turn asserts the per-turn delta of reasoning-block mounts (idx+1),
the minimum card count for each tool group, and unique transcript
substrings that scope to that turn.
3. Playwright e2e spec — showcase/integrations/langgraph-python/tests/e2e/
tool-rendering-reasoning-chain.spec.ts:
- Mirrors the pattern of the sibling tool-rendering-default-catchall spec
(notably its multi-pill regression at lines 162-212).
- Page-loads test verifies the 3 pills mount and no cards leak from a
prior session.
- One test per chained pill (stocks, dice, flights+weather) asserts the
full chain renders with reasoning-block + correct per-tool cards +
narration matching the aimock fixture text.
- Sequential-pills regression test clicks all 3 pills in one thread,
asserts each chain renders independently AND the reasoning-block count
increases monotonically across turns.
Agent: extend `get_stock_price` to accept optional `price_usd` and
`change_pct` arguments (mirrors the basic tool-rendering agent's signature
introduced in #4770). The aimock fixtures script the chained AAPL/MSFT
comparison by passing deterministic prices via these args; without the
wider signature, pydantic rejects the tool call and the card never mounts.
The runtime unit test is the strongest guard — it would catch any
regression on the role-filter logic without depending on the full Docker
stack. The harness probe and Playwright spec catch end-to-end regressions
in the canonical CI environment.
The tool-rendering-reasoning-chain demo previously promised chained tool
calls in its pill titles but the agent and fixtures only delivered single
tools — clicking "Weather + flights to Tokyo" produced just a WeatherCard,
"Compare two stocks" only fetched AAPL, "Find flights from SFO to JFK"
showed flights but no destination weather. Three changes close the gap.
Agent: replace the soft "call 2+ tools when relevant" system prompt with
concrete per-pill chain examples mirroring the pattern already used by the
langgraph-typescript `tool-rendering` agent (weather→flights, ticker→peer,
roll→contrast die, flights→destination weather).
Pills: drop the redundant Tokyo pill (it was the SFO/JFK chain in reverse)
and reword each remaining pill message to PRE-DISCLOSE the chain so the
model commits to the follow-up call:
- "Compare AAPL and MSFT stocks for me."
- "Roll a 20-sided die for me and compare it to a smaller one."
- "Find flights from SFO to JFK and show me the weather there."
Fixtures: 9 fixtures (3 per pill: final-content → second-leg → first-leg,
ordered by toolCallId specificity for first-match-wins). Each fixture is
scoped by a langgraph-python-UNIQUE userMessage tail ("Compare AAPL and
MSFT stocks", "compare it to a smaller one", "show me the weather there").
Those substrings appear nowhere else across the 14+ integrations sharing
showcase-aimock on Railway, so the new fixtures cannot cross-contaminate
the other reasoning-chain demos that still ship the older prompt set.
A toolName-based gate was considered and rejected because most fleet
agents register `roll_dice` and aimock's `toolName` matcher is a tool-LIST
gate, not a tool-CALL gate — it would NOT have isolated this demo.
Probe: collapse the two-turn flow (Tokyo + SFO/JFK) into one chained turn
(SFO→JFK + JFK weather) that asserts BOTH per-tool renderers
(FlightListCard + WeatherCard) mount in a single response. Same coverage
at half the wall-clock and exercises the actual chained-tool path.
`@ag-ui/langgraph`'s message converter handles only user/assistant/system/tool
roles and throws `"message role is not supported."` on anything else. Agents
that stream reasoning summaries (OpenAI Responses API + `reasoning={summary:
"detailed"}`) emit AG-UI messages with `role: "reasoning"` that the AG-UI
client replays in the next turn's `input.messages`; the converter then crashes
before the model is ever called and the second pill click in a multi-turn
thread produces an `INCOMPLETE_STREAM` error.
Strip `role: "reasoning"` from `input.messages` inside CopilotKit's
LangGraphAgent.run subclass before delegating to super. This is the narrowest
fix at the runtime/AG-UI boundary — only the inbound message list is filtered,
the outbound event stream still carries reasoning summaries to the client, so
the `<ReasoningBlock>` slot continues to render on the active turn.
## Summary
Four independent showcase production bugs Alem reported, plus the D5
multimodal harness regression they unblocked. D5 result: **37 → 39 of 40
features passing** on `langgraph-python`. The single remaining failure
(`tool-rendering-reasoning-chain`) is a separate agent/runtime bug —
root cause identified, scoped out below.
## What changed and why
### 1. `shared-state-read-write` — wrong-fixture fallthrough
**Symptom:** "Greet me" returned the generic `showcase assistant` blurb;
"Plan a weekend" returned the generic 5-step content-marketing plan.
Only "Remember something" worked.
**Cause:** The pill prompts `"Say hi and introduce yourself."` and
`"Suggest a weekend plan based on my interests."` were matching the bare
`userMessage: "hi"` / `"plan"` catch-alls in `feature-parity.json`
(which loads after `d5-all.json`). The shared-state demo had no
pill-specific fixtures to win first.
**Fix:** Added two fixtures to `harness/fixtures/d5/shared-state.json`
(mirrored to `aimock/d5-all.json`) keyed on longer unique substrings.
Responses now reference the Preferences panel / interests panel so the
demo's intent is clear.
### 2. `auth` — sign-out never demonstrates rejection
**Symptom:** Clicking "Sign out" unmounted `<CopilotKit>` and bounced
the user back to the SignInCard. The demo's whole point — runtime
returning 401 on unauthenticated requests — was never visible.
**Cause:** `page.tsx` rendered SignInCard whenever `!isAuthenticated`,
which included the post-sign-out state.
**Fix (matches `qa/auth.md` spec):**
- `useDemoAuth` tracks a `hasEverSignedIn` flag (in-memory; resets on
full page reload).
- `page.tsx` only shows SignInCard for first-paint visitors; signed-in
and signed-out states both keep `<CopilotKit>` mounted.
- `AuthBanner` is bi-state — green when authed, amber when signed out —
and exposes a new `data-testid="auth-authenticate-button"` for
re-sign-in.
- `<CopilotKit onError={...}>` populates a
`data-testid="auth-demo-error"` surface so the 401 from the runtime is
visible to the user the moment they send an unauthenticated message.
- Updated `tests/e2e/auth.spec.ts` accordingly (the old "SignInCard
re-mounts after sign-out" test was pinning the regression).
### 3. `gen-ui-agent` — step progression short-circuited
**Symptom:** Clicking a pill rendered `All 3 steps complete` instantly.
No pending → in_progress → completed animation. The card just appeared
in its final state.
**Cause:** The aimock fixture emitted a single `set_steps` tool call
with all three steps already `status: completed`, even though
`gen_ui_agent.py`'s SYSTEM_PROMPT instructs the LLM to make seven
sequential calls (1 all-pending seed + 6 transitions).
**Fix:** Regenerated `gen-ui-agent.json` as a 7-leg `toolCallId` chain
per pill (24 fixtures total = 3 pills × 8 legs each):
- Seed leg keyed only on `userMessage` (no `hasToolResult` gate —
matches the pattern PR #4770 established, since `hasToolResult: false`
blocks the seed firing on subsequent pills in a multi-pill session).
- Six `toolCallId`-keyed transitions advancing each step pending →
in_progress → completed in order.
- Final narration after the 7th set_steps' tool result.
- Order: `toolCallId` legs FIRST, `userMessage` seed LAST, so the most
specific matcher wins first-match-wins.
Mirrored verbatim into `d5-all.json`. Added a regression test in
`tests/e2e/gen-ui-agent.spec.ts` that asserts at least one step is
observed in `pending` state during the run.
### 4. Multimodal D5 — runner sends a competing message
**Symptom:** D5 multimodal failed with `timeout: assistant did not
respond within 30000ms (baseline=0, current=0)` even though the
LangGraph agent's runs all succeeded.
**Cause:** The sample-attachment buttons auto-send the user message via
`agent.addMessage` + `copilotkit.runAgent` (the behavior PR #4761
restored). But the D5 conversation runner ALSO typed `input` and pressed
Enter immediately after `preFill`, sending a SECOND user message. Both
messages were matched and processed by aimock (debug logs confirm), but
the resulting v1 LangGraph runtime SSE stream got tangled — Chrome
DevTools showed `POST /api/copilotkit-multimodal` stuck in `statusCode:
pending`, and the assistant message never rendered.
**Fix (3 small pieces):**
- `conversation-runner.ts`: added `skipSend?: boolean` to
`ConversationTurn`. Distinct from the existing `skipFill` (which still
presses Enter once the textarea has content) — `skipSend` skips both
fill and Enter entirely. Used when `preFill` handles the whole
submission via the agent surface.
- `d5-multimodal.ts`: set `skipSend: true` on both image and PDF turns,
bumped `responseTimeoutMs: 60_000` (the Playwright e2e spec uses 60-90s
for the same path).
- `feature-parity.json`: PDF auto-prompt fixture now says "The attached
PDF document is..." instead of "The attached PDF is..." so the existing
`buildModalityAssertion("document")` check still lands.
## Out of scope (separate task)
- **`tool-rendering-reasoning-chain`** — the one remaining D5 failure.
Root cause: the Tokyo pill uses Responses-API mode, producing a
`reasoning` role message in the conversation. On Turn 2, that message
survives in history and the runtime returns `RUN_ERROR: "message role is
not supported"`. This is an agent/runtime issue, not a fixture-level
fix.
- **`book-a-call` Cancel path** — clicking "None of these work" produces
a contradictory "Booked!" narration. Aimock's JSON-fixture matcher can't
inspect tool-result content (only `userMessage` + `toolCallId` +
`hasToolResult` + `toolName`), so this needs either a React-level UI
redesign or an aimock package extension.
## Test plan
- [x] `bin/showcase fixtures validate` — 0 errors
- [x] `bin/showcase test langgraph-python --d5` — 39 of 40 features
passing (was 37 before)
- [x] Manual verification of `/demos/auth` end-to-end on local: sign in
→ sign out → send unauthenticated message → 401 error surface displays
with banner still amber, no white-screen
- [x] Manual verification of `/demos/shared-state-read-write` on local:
Greet me and Plan a weekend return shared-state-aware responses instead
of the generic catch-alls
- [x] Direct aimock probe (`curl /v1/chat/completions`) verifies the
`set_steps_launch_001` → `_002` → ... → `_007` chain emits the expected
progression states
- [ ] New `tests/e2e/shared-state-read-write.spec.ts` Playwright suite
(4 tests including the wrong-fixture regression assertions) — runs in CI
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Four independent showcase production bugs Alem reported, plus the
D5 multimodal harness regression they unblocked.
Shared-state-read-write: "Greet me" ("Say hi and introduce yourself.")
and "Plan a weekend" ("Suggest a weekend plan based on my interests.")
were matching the bare `hi` and `plan` catch-alls in feature-parity.json
and returning the generic showcase-assistant blurb / 5-step content plan
instead of shared-state-aware responses. Added pill-specific fixtures in
shared-state.json (mirrored into d5-all.json) so the longer userMessage
substrings win first-match-wins ahead of feature-parity.
Auth sign-out: signing out unmounted CopilotKit entirely and bounced
the user back to the SignInCard, so the demo never showcased the
runtime returning 401 — its whole point. The QA contract in
qa/auth.md spelled out the intended UX. Restored it: CopilotKit stays
mounted after the first sign-in, the AuthBanner flips to an amber
"Signed out — the agent will reject your messages" state with a
re-Sign-in button, and CopilotKit's `onError` callback drives a
`data-testid="auth-demo-error"` surface that displays the runtime's
401 the moment the user sends an unauthenticated message. Updated the
e2e spec to match (the old "SignInCard re-mounts after sign-out" test
pinned the regression).
Gen-ui-agent: the aimock fixture short-circuited the 7-step
progression spelled out in `gen_ui_agent.py`'s SYSTEM_PROMPT to a
single set_steps call with all three steps already `completed`, so
the InlineAgentStateCard rendered the final 3/3 state instantly with
no sequential pending → in_progress → completed animation.
Regenerated as a 7-leg toolCallId chain per pill (8 fixtures × 3
pills): seed leg keyed on userMessage with NO `hasToolResult` gate
(matching PR #4770's pattern — `hasToolResult: false` would block the
seed from firing on the second pill in a multi-pill session), then
six toolCallId-keyed transitions, then a final narration. Fixture
order: toolCallId legs FIRST so the most specific match wins.
Multimodal D5: the sample-attachment buttons auto-send via
`agent.addMessage + copilotkit.runAgent` (restored in PR #4761), but
the D5 harness still typed `input` + pressed Enter via the runner
after `preFill`, sending a second user message that competed with the
in-flight image upload — the v1 LangGraph runtime SSE stream got
tangled (browser DevTools showed `statusCode: pending` indefinitely)
and the assistant message never rendered. Added `skipSend?: boolean`
to ConversationTurn (distinct from `skipFill`, which still presses
Enter once the textarea has content) and switched d5-multimodal.ts to
`skipSend: true` with `responseTimeoutMs: 60_000` so the runner waits
on the assistant response without poking the chat further. Bumped the
PDF auto-prompt fixture in feature-parity.json to include the word
"document" so the existing `buildModalityAssertion("document")` check
still lands.
D5 result: 37 → 39 of 40 features passing. Only
`tool-rendering-reasoning-chain` remains and is a separate
agent/runtime bug (Tokyo Responses-API `reasoning` message survives
into the next turn's conversation history, runtime returns
`RUN_ERROR: "message role is not supported"`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the CopilotKit runtime's extractForwardableHeaders() which
already forwards all x-* prefixed headers. Enables any custom
x-* header to propagate from browser through AG-UI to LLM calls.