chore(frontend): bump A2A, AI SDK v6, streamdown v2; re-sync AI Elements#2389
chore(frontend): bump A2A, AI SDK v6, streamdown v2; re-sync AI Elements#2389malinskibeniamin merged 38 commits intomasterfrom
Conversation
Captures 13 PNG screenshots of the adopted AI-Elements variants — Tool card states (incl. the three newly adopted MCP approval flow states and DynamicToolUIPart), ConversationDownload, Context hover-card guards, and the Shimmer loading frame — rendered via vitest browser mode and committed under docs/pr-screenshots/ for reference in the PR body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude please review this PR. Specifically focus on:
Flag any concerns even if minor. Don't worry about pre-existing lint noise. |
|
Claude finished @malinskibeniamin's task in 3m 16s —— View job PR ReviewOverall this is a well-executed bump. The v6 compatibility path is sound and the ai-elements re-sync is surgical. A few findings below, ordered by severity. 1. Bug —
|
|
Fix screenshot paths. They don't render properly |
Static tools passed `type="tool-foo-bar"` with no `toolName` prop, so the copy button title rendered "Copy: undefined". Use `displayName` instead, which resolves to `title ?? derivedName` — already the visible label. Also fix the lint regressions introduced by the previous refactor: formatter drift in event-handlers.ts and a dangling `continue` left after removing the text-delta branch in use-message-streaming.ts. Caught by @claude PR review on #2389. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eline PostCSS' strict @import-first rule was rejecting globals.css under the vitest browser-mode pipeline because @source came before @import. Moving @source after the @imports satisfies PostCSS while still letting tailwind v4 scan the streamdown bundle for utility classes. Verified the rsbuild production build is unaffected. Browser-setup now imports globals.css so vitest-browser screenshots render with the real theme + tailwind utilities instead of browser defaults. Unblocks showcase PNGs for PR #2389. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @malinskibeniamin's task in 2m 8s —— View job PR Review
|
…itting The 13 showcase PNGs used in PR #2389's description now live on the orphan branch `pr-screenshots-2389` and are referenced via raw URLs, so the main merge history stays free of binary assets. The browser tests still regenerate PNGs locally under `frontend/docs/pr-screenshots/`; that directory is gitignored so re-runs do not re-commit them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align Redpanda Console with the current stable versions of the AI toolchain. All four packages are dependencies of the AI Agents chat feature: - @a2a-js/sdk 0.3.10 → 0.3.13 (patch: stream/task fixes, no API change) - @modelcontextprotocol/sdk 1.26.0 → 1.29.0 (minor additive) - ai 5.0.101 → 6.0.168 (new major) - streamdown 1.4.0 → 2.5.0 (new major) ai v6 ships a new @ai-sdk/provider v3 package that introduces LanguageModelV3, but keeps the v2 types as a backwards-compat export and accepts either in the public LanguageModel alias, so our custom A2aChatLanguageModel (which implements LanguageModelV2 from @ai-sdk/provider) continues to work without modification. streamdown v2 only changes internals (remark-cjk/katex plumbing) and is source-compatible with how response.tsx consumes it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ai v6 reshaped LanguageModelUsage: inputTokenDetails and outputTokenDetails are now required sub-objects, and the pre-v6 top-level reasoningTokens/cachedInputTokens fields are deprecated but still present. Our useContextUsage hook returned only the legacy fields, which tsgo flagged as missing required members once the alias pulled in the v6 shape. Populate both the new sub-objects and the legacy fields so the Context component (which still reads from the deprecated top-level fields) continues to work today and any future upstream re-sync that switches to the new fields sees consistent values. Add a unit test covering the mapping, useMemo stability, and recompute behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ments Tool Re-sync the Tool component with vercel/ai-elements upstream to pick up support for the ai v6 tool-approval flow. ai v6 introduced three new ToolUIPart states used by the MCP approval loop: - approval-requested → "Awaiting Approval" (shield/alert) - approval-responded → "Responded" (shield/check) - output-denied → "Denied" (X) It also added DynamicToolUIPart for provider-resolved (e.g. MCP) tools, where the tool name isn't encoded in the part type and must be passed via a toolName prop. ToolHeader now accepts a discriminated union: either a static `tool-*` type with no toolName, or `dynamic-tool` with toolName. Redpanda-specific customisations are preserved: our own Badge variants (success-inverted / info-inverted / destructive-inverted / neutral-inverted / warning-inverted), the durationMs display, the toolCallId copy button, and the deepParseJson-aware ToolInput / ToolOutput behaviour. Covered by new tests in tool.test.tsx for all seven states plus the dynamic-tool naming, title override, duration formatting, and toolCallId rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull the upstream ConversationDownload component and messagesToMarkdown helper from vercel/ai-elements into our vendored conversation.tsx. The AI Agents chat feature will use these to give users a one-click export of an agent conversation as Markdown. Kept all Redpanda customisations: import path to components/redpanda-ui/components/button, overflow-y-auto (intentional over upstream's overflow-y-hidden so the content scrolls), simpler ConversationContent classes, no dark:bg-background override on the scroll button (our Button variant handles theming), and the Conversation.displayName. Covered by a unit test of messagesToMarkdown across role capitalisation, multi-message joining, multi-part text concatenation, non-text parts, custom formatters, and the empty case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pick up three small upstream improvements from vercel/ai-elements while preserving every Redpanda customisation (import paths, Redpanda Button variants, intentional class overrides): - shimmer.tsx: cache motion components in a module-level Map so that `motion.create(element)` is never called during render. Stops churning component identity across every message tick. - context.tsx: memoise the React context value so consumers only re-render when a usage field actually changes, and guard ContextInputUsage / ContextOutputUsage against a zero-token state so the HoverCard never shows "Input —" lines before a response has arrived. - image.tsx: prefix the unused destructured `uint8Array` arg with an underscore, matching the upstream biome convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@modelcontextprotocol/sdk upgrade will ship in its own PR so this one stays scoped to the AI toolchain (A2A + ai v6 + streamdown v2 + ai-elements re-sync). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the JSON-RPC error parser and its five regex constants out of
use-message-streaming.ts into chat/utils/parse-a2a-error.ts. The hook
imports from the new module; the existing describe('parseA2AError', ...)
suite in use-message-streaming.test.ts updates its import path only.
Function signature and behavior are unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dedicated test.each suite targeting the extracted util directly. Covers
the SDK's two serialized JSON-RPC error shapes (SSE-wrapped and
streaming), invalid Data: JSON, empty/missing code fallbacks, non-Error
input types, and prefix-stripping behavior. The existing hook-level
describe('parseA2AError') in use-message-streaming.test.ts stays put
since it exercises the same util through the full streamMessage path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…itting The 13 showcase PNGs used in PR #2389's description now live on the orphan branch `pr-screenshots-2389` and are referenced via raw URLs, so the main merge history stays free of binary assets. The browser tests still regenerate PNGs locally under `frontend/docs/pr-screenshots/`; that directory is gitignored so re-runs do not re-commit them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
994fa1e to
34b9465
Compare
|
@claude review again |
|
Claude finished @malinskibeniamin's task in 3m 9s —— View job PR Review
Response to Inline Review Comments
Copyright years — "Use 2026 copyright everywhere"
const usedPercent = Number.isFinite(maxTokens) && maxTokens > 0
? usedTokens / maxTokens
: 0;
Still-Unfixed Issues from Previous Reviews1. Double-send bug — non-streaming fallback ( This was flagged in both prior reviews and remains unaddressed: // line 312 — fires unconditionally even when simulatedStream is set
const response = client.sendMessageStream(streamParams);When Fix: move 2. Dead ternary in className={cn("rounded-md", errorText ? "bg-muted/50" : "bg-muted/50")}Both branches are identical — error and success output containers look the same. Likely intended New Observation
SummaryCopyButton title fix is confirmed applied. The v6 adapter and |
The 32k PNG baseline under __screenshots__/ doesn't need to live in the repo — our convention is to regenerate locally or host showcase screenshots on the pr-screenshots orphan branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @malinskibeniamin's task in 2m 39s —— View job PR Review
What's been resolved since the last reviewAll inline review comments are addressed:
Still-unfixed — both flagged in all three prior reviews1. Double-send in non-streaming fallback ( if (!clientCard.capabilities.streaming) {
const nonStreamingResponse = await client.sendMessage(streamParams); // request 1
if ("result" in nonStreamingResponse) { simulatedStream = new ReadableStream(...); }
if ("error" in nonStreamingResponse) {
// FIXME: error ← still silently swallowed
}
}
// line 312 — fires unconditionally regardless of simulatedStream
const response = client.sendMessageStream(streamParams); // request 2, alwaysBoth problems remain:
Two-line fix: move 2. Dead ternary in <div className={cn("rounded-md", errorText ? "bg-muted/50" : "bg-muted/50")}>Both branches are identical — error and success containers look the same. Intended New minor observation
export const JSON_RPC_DATA_REGEX = /Data:\s*(\{[^}]*\})/i;
Architecture note (resolved concern)
SummaryStrong improvement round. Every inline review comment is addressed and the new additions ( |
A non-streaming agent previously received the message twice — once via the sendMessage branch (whose result populated simulatedStream) and again unconditionally via sendMessageStream. The FIXME error branch also silently fell through instead of surfacing the error. Move sendMessageStream into an else branch so the two transport paths are mutually exclusive, and throw on sendMessage error so failures propagate instead of being shadowed by the subsequent stream. Flagged by @claude PR reviewer on #2389. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ternary on the result container had bg-muted/50 in both branches, so errors looked identical to successful results. Switch the error branch to bg-destructive/10 and add a regression test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The [^}]* fragment stopped at the first closing brace so nested data got truncated and JSON.parse silently threw, losing the structured payload. Anchor to end-of-string with the /s flag and add a nested-object test case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ort selection Pull the streaming-vs-blocking decision out of doStream into a pure function that takes a structural A2ATransport. Lets us cover the double-dispatch fix with deterministic unit tests (4 cases: streaming path, blocking success, blocking error, undefined capability). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- hoist regex literal to module scope for useTopLevelRegex - rename inline generator to reuse across assertions for useAwait - apply numeric separators via biome --write No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The renderHook-based tests in remote-mcp.test.tsx and the streaming progress UI tests in remote-mcp-inspector-tab.test.tsx triggered component state updates outside of act() when mutateAsync settled or when onprogress callbacks were fired directly, producing React warnings in test stderr. Wrap the triggering awaits in act(async () => ...) to flush them deterministically. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Summary
Upgrade the AI Agents chat toolchain to current majors and pull in the
vercel/ai-elementsfeatures the UI needs (MCP tool-approval flow,conversation markdown export), preserving all Redpanda customisations.
Versions
@a2a-js/sdk^0.3.10^0.3.13ai^5.0.101^6.0.168streamdown^1.4.0^2.5.0Why this is safer than it looks
aiv6. Our customA2aChatLanguageModelimplementsLanguageModelV2.v6 keeps the V2 types exported and accepts them via the public
LanguageModelalias (V3 | V2), so no adapter rewrite is needed.The one shape change we hit is
LanguageModelUsagerequiringinputTokenDetails/outputTokenDetails;useContextUsagewasupdated accordingly.
streamdownv2. Purely internal remark/katex plumbing changes;our
response.tsxconsumer is source-compatible.ai-elements. Every upstream file was diffed against ours anddeltas applied surgically. Redpanda-specific customisations
(theming, token-count avatars, Badge variants, artifact metadata)
are preserved verbatim.
What we adopted from upstream ai-elements
ConversationDownload+messagesToMarkdownhelperDynamicToolUIPartsupport and three new tool states:approval-requested,approval-responded,output-deniedshimmerWhat we deferred (follow-up PRs)
prompt-input.tsxmerge (heavy divergence, needs its own focused pass)loader.tsx→ shadcn/uiSpinnermigration (upstream deprecation)ChainOfThought,Reasoning,Attachments,PromptInputActionAddScreenshot(not wired into the agents UI yet)Risk and verification
for the adopted behavior (
tool,conversation,use-context-usage).usage hook needed the new sub-object fields.
Screenshots
Captured via vitest browser mode (Chromium, headless, viewport 1440×900,
reduced-motion). Source tests live under
frontend/src/components/ai-elements/*.browser.test.tsx.Tool states
input-streaminginput-availableoutput-availableoutput-errorapproval-requested(new)approval-responded(new)output-denied(new)DynamicToolUIPartwithtoolNameoverride (new)ConversationDownload
Context hover-card
Shimmer
Test plan
bun install && bun start, open an AI Agents chat.variants, duration, and
toolCallId.new states render (Awaiting Approval → Responded / Denied).
still display, zero-token rows hidden.
ConversationDownloadproduces a well-formedmarkdown file.
🤖 Generated with Claude Code