## Summary
- During streaming, `AbstractAgent.addMessage()` can push messages with
the same `id` into the messages array, causing React duplicate key
warnings in `CopilotChatMessageView`
- Added Map-based deduplication in the message rendering pipeline —
last-write-wins for duplicate IDs
- Includes dev-mode `console.warn` when duplicates are detected, to
surface the upstream cause without breaking the UI
Fixes#3258
## Test plan
- [x] Unit tests added verifying deduplication behavior
- [x] Warning logged in development when duplicates detected
- [x] Full package test suite passes
- [x] Build passes
🤖 Generated with [Claude Code](https://claude.ai/code)
Reverts the resolveAgent extraction from the previous commit — the
original useMemo + ref cache already provides referential stability.
All 4 stability tests pass without the refactor.
runtime-client-gql's graphql-codegen needs the schema file generated
by @copilotkit/runtime's build. After removing the package.json
dependency (to fix bundle size), Nx no longer orders the builds
correctly. implicitDependencies restores build ordering without
reintroducing the runtime dependency.
The @copilotkit/runtime-client-gql package listed @copilotkit/runtime as
a dependency, but none of its source files actually import from it. This
caused the entire server-side runtime (graphql-yoga, hono, openai, shiki,
reflect-metadata, etc.) to be pulled into client bundles, exploding
bundle size from ~600kb to 4.3mb+.
Remove the unused dependency from package.json, the UMD externals list,
and the UMD globals map in tsdown.config.ts.
Move V1 test file to __tests__/ directory and add test verifying that
string enum constraints round-trip through convertJsonSchemaToZodSchema.
Add equivalent test for V2's copy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The string case in convertJsonSchemaToZodSchema produced z.string() and
silently discarded the enum field. When useCopilotAction defines a
parameter with enum: ["todo", "done"], the constraint was lost during
the V1→V2 bridge and never reached the runtime payload.
Add an enum check before falling through to z.string() in both V1
(shared/json-schema.ts) and V2 (agent/index.ts) copies of the function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The afterRequestMiddleware receives a cloned Response (via c.res.clone()),
whose internal stream controller functions have different names across
Node versions. Using expect.any(Response) avoids brittle deep equality
on Response internals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clone Response before body is consumed, parse SSE stream to reconstruct
messages. Adds messages, threadId, runId to AfterRequestMiddlewareParameters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Next.js rejects global CSS imports from within node_modules, causing
build failures when consumers use @copilotkitnext/react:
Global CSS cannot be imported from within node_modules.
Location: node_modules/@copilotkitnext/react/dist/index.mjs
The static `import 'katex/dist/katex.min.css'` in
CopilotChatAssistantMessage.tsx gets preserved in the dist bundle
(tsdown unbundle mode), triggering the Next.js css-npm restriction.
Replace with a useKatexStyles() hook that dynamically imports the CSS
at runtime via useEffect. Dynamic imports bypass Next.js static
analysis and load the stylesheet lazily when the component mounts.
Fixes#2851
The shouldCreateModalState guard introduced in #3101 made setModalOpen
and isModalOpen conditionally undefined when isModalDefaultOpen wasn't
explicitly passed. This broke programmatic modal control for anyone
using useCopilotChatConfiguration outside of CopilotPopup/CopilotSidebar.
Reverts to the simpler ?? fallback pattern: each provider always has
modal state, preferring parent state when available, falling back to
its own internal state otherwise. Initial state still respects the
local isModalDefaultOpen prop (fix from 76e381f78).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The V1 CopilotKit provider hardcoded useSingleEndpoint={true}, preventing
users from opting into REST transport mode. Now passes through the user's
prop value, defaulting to true for backward compatibility.