The previous regression (#5041, shared root cause with #4739) slipped through
because the original coverage (use-agent-thread-isolation.test.tsx) lived
next to the per-thread-cloning feature and was deleted alongside it when
cloning was reverted. The invariant outlived the feature but the tests didn't.
Relocate to packages/react-core/src/__tests__/ and rename as a contract test
so future implementation swaps (cloning, effect, prop drilling, context) keep
it in scope. Tightened the header docstring to spell out the invariant and the
reason for the placement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useAgent now syncs agent.threadId from CopilotChatConfigurationProvider when
the caller marked the threadId as explicit. Without this, AbstractAgent's
constructor mints a random UUID and ProxiedCopilotRuntimeAgent ships it in
/agent/run, /agent/connect, /agent/stop — diverging from the threadId app code
reads via useThreads, breaking thread persistence and causing 404s on lookup.
This was originally fixed by per-thread agent cloning in #3525. That cloning
was reverted in May 2026 because it wiped state on tool calls, and the revert
only restored the explicit assignment in V2 CopilotChat — leaving headless
useAgent (issue #4739) and the V1 chat hook path unfixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zod 4 made the key schema mandatory for z.record, so the single-argument
z.record(valueType) form is a compile-time error (TS2554) when built against
Zod 4. @copilotkit/react-core declares zod ">=3.0.0", so downstream apps on
Zod 4 are affected; runtime parsing is unaffected under both majors.
- react-core + vue MCPAppsActivityContentSchema: toolInput now uses the
two-argument z.record(z.string(), z.unknown()) form
- react-core defineToolCallRenderer test: same fix for a metadata schema
- add a toolInput field-contract test (round-trips mixed value types)
- add copilotkit/no-single-arg-zod-record oxlint rule (autofix), enabled as
error for packages/**; the incompatibility is type-level, so no runtime
test can guard it while the workspace lockfile pins Zod 3
Closes#4295
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up per-request header forwarding (onRequest hook + headerFactory)
and the prepareStream configurable+context partition fix from
ag-ui-protocol/ag-ui#1763. Together with copilotkit==0.1.91 on the
Python side (R3a), this unblocks D6 LGP/LGT header propagation.
The mergeConfigs() change in 0.0.33 also fixes the HTTP 400 from
langgraph-api 0.7+ when both configurable and context are present.
Bumped in two files:
- packages/runtime/package.json: 0.0.31 -> 0.0.33
- packages/sdk-js/package.json: 0.0.31 -> 0.0.33
Added @ag-ui/langgraph to minimumReleaseAgeExclude in .npmrc.
pnpm-lock.yaml regenerated.
Showcase auto-redeploys on merge via showcase_build.yml.
Replace docs URLs that currently 301 through the legacy redirect catalog
with their canonical post-cutover destinations so users clicking links
from console warnings, JSDoc, and in-product help land in one hop.
URLs updated:
- /premium#how-do-i-get-access-to-premium-features
-> /premium/overview#getting-access
- /coagents/quickstart/langgraph -> /langgraph-python/quickstart
- /coagents/shared-state/predictive-state-updates
-> /langgraph-python/shared-state/predictive-state-updates
- /reference/v1/hooks/useCopilotChatHeadless_c
-> /reference/v2/hooks/useCopilotChatHeadless_c
- /coagents/troubleshooting/common-issues
-> /langgraph-python/troubleshooting/common-issues
- /quickstart#get-a-copilot-cloud-public-api-key
-> /built-in-agent/quickstart#create-a-free-account
- /premium -> /premium/overview
URLs left as-is because they already resolve 200 with no redirect:
/migration-guides/migrate-attachments, /migration/render-message,
/telemetry.
Hook bypassed: pre-commit test failed in @copilotkit/web-inspector due
to missing jsdom dependency in its package.json (unrelated to this
change; no overlap with edited files or URLs). Tests for the four
affected packages (react-core, react-ui, shared, runtime) pass.
Add peer dependencies, export new components and hooks from package entry point, integrate RenderToolProvider into CopilotKitProvider, configure vitest and tsdown, add usage documentation.
FlatList-based chat interface and bottom-sheet modal overlay. Includes suggestion pills, keyboard avoidance, custom FlatList support, and comprehensive test coverage.
Hook for rendering custom tool UIs in React Native with a store-based context provider. Includes tests for register/unregister, subscriber notification, and error handling.
Message bubble components with streaming markdown support, typing indicator animation, and timestamp formatting. Includes unit tests and edge case coverage.
Packages without repository.url fail npm OIDC provenance verification.
Adds the field to agentcore-runner, core, sqlite-runner, voice, and
web-inspector. Includes a one-shot workflow to publish the 14 remaining
v1.57.4 packages (a2ui-renderer already published via OIDC).
- Rework shared helper: parseAndWarnTelemetryId returns parsed id AND
warns, so both v1 and v2 setLicenseToken call it once without
inlining duplicate code or double-parsing the JWT.
- Fix v1 sampleWeight bug: identified events bypass the sample gate
and ship at effective rate 1.0, so a single global sampleWeight =
1/sampleRate would overweight identified-customer counts by
1/sampleRate (20x at the 0.05 default). Move sample metadata
(sampleRate / sampleRateAdjustmentFactor / sampleWeight) out of
globalProperties and compute per-event using effectiveSampleRate.
- Guard setSampleRate against parseFloat("nonsense") = NaN slipping
past the range check. With the default now 0.05, env-var overrides
are more common and a typo would otherwise produce silent
always-drop.
- Add tests: sampleWeight differs for identified vs anonymous,
malformed JWT stays anonymous, license-token cache is overwritable,
NaN env override is rejected, v2 default sampleRate = 0.05 is pinned.
Cache parsed telemetry_id at setLicenseToken time and use it in capture()
to branch on identified vs anonymous. Identified callers (token with
telemetry_id) always send; anonymous callers are sampled at sampleRate.
Default sampleRate changes from 1.0 to 0.05 so the anonymous OSS-runtime
firehose is capped at the client. Identified customers continue to send
at full fidelity.
Cache parsed telemetry_id at setLicenseToken time and use it to branch
in capture():
- Identified callers (token with telemetry_id) always send to both sinks.
- Anonymous callers are sampled at sampleRate (default 0.05); one dice
roll gates both lambda and Segment.
The Lambda no longer needs to bypass-from-sampling for identified
events — that decision moves entirely to the client. Reduces lambda
invocations by ~95% for the anonymous OSS-runtime firehose.
Operators currently get silent attribution loss if a license token is
configured but parses without a telemetry_id field — useful as a smoke
signal during the issuer rollout, when older licenses lack the field
entirely.
Each TelemetryClient setter (v1 shared, v2 singleton) now calls
parseTelemetryIdFromLicense at configuration time and emits a one-shot
console.warn when the result is null. No per-event spam.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the Lambda/Segment specifics in favor of an implementation-neutral
description: these fields aren't used by the telemetry service, so we
strip them at the wire boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CopilotCloud customer key (`ck_<env>_<id>.<secret>`) is routed to
Segment for downstream user analytics, but has no role in the
telemetry-sink Lambda. Worse, the secret half should never leave the
customer's runtime.
Strips both wire-format variants at the lambda-client boundary:
- `cloud.public_api_key` (v2 event property convention)
- `cloud.publicApiKey` (v1 globalProperties from setCloudConfiguration)
The strip happens at the lambda-client wire layer rather than in each
caller, so any future caller (or accidental property regression) is
covered by default. Boolean indicators like `cloud.api_key_provided`
and unrelated fields like `cloud.baseUrl` continue to ride through.
New unit test (`lambda-client.test.ts`) exercises the strip with a real
fetch spy, plus end-to-end JWT extraction including the
no-`telemetry_id` and not-a-JWT fallback paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CopilotCloud customer API key (`ck_<env>_<id>.<secret>`) is unrelated
to telemetry attribution — it flows into Segment/PostHog only. The
attribution signal lives in the EIP / Intelligence license JWT, whose
payload carries `telemetry_id` (alongside license_id, owner.org_id,
features, etc.).
Rewires the lambda-client to base64url-decode the license JWT payload
and emit X-CopilotKit-Telemetry-Id from `telemetry_id`. No signature
verification — that's license-verifier's job, and the Lambda is
claim-only by design.
Plumbing:
- Shared TelemetryClient (v1) and v2 telemetry singleton each get a
`setLicenseToken` setter; the v1 client drops `apiKey:` from its
lambdaClient.send call, the v2 client drops the
cloud.public_api_key extraction from event properties.
- Both runtime constructors call `telemetry.setLicenseToken(...)` once,
resolving `options.licenseToken ?? process.env.COPILOTKIT_LICENSE_TOKEN`
to match license-verifier's own env-fallback. Without that, customers
who set only the env var would get a working licenseChecker but
anonymous telemetry.
Tests: v2 telemetry test refreshed — old "cloud api key extraction"
assertion replaced with one that confirms cloud.public_api_key rides
in properties (not as licenseToken), and a new test asserts that
setLicenseToken plumbs through to lambdaClient.send.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The HMAC scheme bound identity to "holder of API key X," but since the
secret is shipped inside distributed customer keys it never actually
prevented a determined attacker from impersonating that customer — and
the Lambda still accepted unsigned requests anyway, so the signing path
provided attribution, not abuse control.
Replaces ~85 lines of Web Crypto / HMAC / nonce / canonical-string
machinery with a single `X-CopilotKit-Telemetry-Id: <id>` header. The
SDK now extracts the id from `ck_<env>_<id>.<secret>` keys and ignores
the secret half. Anonymous sends (no/legacy keys) are unchanged.
Drops the implicit Node ≥19 / edge-runtime requirement that the
WebCrypto path imposed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locks in the production telemetry-sink endpoint (the API Gateway custom
domain backed by the ingest Lambda). Customers can still override with
COPILOTKIT_TELEMETRY_URL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds @copilotkit/shared/telemetry/lambda-client which posts events to a
CopilotKit-controlled telemetry-sink endpoint, replacing the direct Scarf
calls in both v1 (shared) and v2 (runtime) telemetry clients. When the
configured CopilotCloud API key parses as the new ck_<env>_<id>.<secret>
format, the request is HMAC-signed (CK1, sha256 over ts/nonce/body) so
the sink can verify and enrich with the customer email; otherwise it
falls through to an unsigned send (legacy keys, OSS-only installs). v1
keeps its existing Segment path with 5% client sampling; v2 sends 100%
to the sink and lets the sink sample server-side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The if (agent.headers) guard in configureAgentForRequest silently
skipped header forwarding when agent.headers was undefined (the
default for LangGraphAgent). This meant x-aimock-context, x-test-id,
and other x-* headers were never forwarded to agent backends.
Also wires install_httpx_hook in the Python SDK middleware so
forwarded headers propagate to outgoing LLM API calls.
Closes the gap documented in PR #4773 spec as out-of-scope.
- Replace JS error.message mutation with wrapped Error + cause chain
(safe for frozen errors, shared references, non-Error throwables)
- Rename dispatched_end → end_attempted, set before END dispatch to
prevent duplicate TOOL_CALL_END when END partially flushes before throwing
- Switch JS randomId() (ck-prefixed) to randomUUID() for cross-SDK parity
with Python's str(uuid.uuid4())
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap JS dispatchCustomEvent in try/catch that enriches error messages
with tool name and ID for debuggability
- Apply asyncio.shield to copilotkit_emit_message's post-dispatch sleep
to match copilotkit_emit_tool_call's behavior under task cancellation
- Reorder validation in LangGraph Python and JS to name → toolCallId →
args, matching CrewAI's order (cheap checks before serialization)
- Narrow AG-UI dispatcher's except clause around json.dumps from
Exception to (TypeError, ValueError), matching sibling SDK variants
- Add CancelledError propagation and warning-log tests for the shielded
sleep path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Re-raise CancelledError after logging in langgraph copilotkit_emit_tool_call
to honor asyncio cancellation contract (was silently un-cancelling tasks)
- Move dispatched_end flag to after ToolCallEndEvent dispatch in AG-UI agent
so compensating END fires when END itself throws
- Add dispatched_end tracking to CrewAI variant to prevent double-END on
partial failure
- Add JSON.stringify(args) validation in JS SDK matching Python parity
- Add 4 CrewAI compensating-END tests and 1 JS serializability test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address code review findings across all three SDK variants:
- Shield asyncio.sleep(0.02) with asyncio.shield() so task cancellation
doesn't prevent returning the tool_call_id after dispatch
- Revert args validation to original permissiveness (JS: undefined-only
check, Python: no isinstance check) to avoid breaking existing callers
- Add upfront json.dumps() serializability check in Python variants
- Fix compensating TOOL_CALL_END double-emit by tracking dispatched_end
- Add compensating action_execution_end to CrewAI variant (queue_put is
non-atomic)
- Use exc_info=True in compensating-END error logging
- Export all exception types from copilotkit package root (__init__.py)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing @returns tag to JS copilotkitEmitToolCall JSDoc, and add
4 tests for the AG-UI compensating TOOL_CALL_END error-recovery path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address code review findings:
- Wrap AG-UI tool call dispatch in try/except with compensating
TOOL_CALL_END to prevent clients hanging on partial emission
- Reject non-dict/non-str args at the dispatch layer (lists, ints, None)
- Guard against None event value before calling .get()
- Fix docstring examples that reuse variable names (won't compile)
- Introduce CopilotKitError base class; all exceptions now inherit from
it; CopilotKitMisuseError inherits from both CopilotKitError and
ValueError
- Add missing validation tests for name and args across LangGraph and
CrewAI Python variants, plus AG-UI dispatch edge cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address code review findings: stop mislabeling dispatch errors as
CopilotKitMisuseError in JS (let them propagate naturally), add
CopilotKitMisuseError(ValueError) to Python SDK, pre-serialize args
in AG-UI handler to prevent partial event emission, align whitespace
validation across all SDKs and the dispatch layer, tighten JS args
type to Record<string, unknown>, and add comprehensive negative tests
for AG-UI dispatch validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore CopilotKitMisuseError for dispatch failures in JS (was bare Error)
- Rename JS options.id to options.toolCallId for cross-SDK naming parity
- Add name/args validation to Python LangGraph and CrewAI variants
- Add defensive field validation in AG-UI dispatch handler
- Add missing CrewAI whitespace-only ID test
- Add JS dispatch failure test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align JS whitespace-only ID rejection with Python (.trim()), show
returned ID in docstring examples, strengthen CrewAI test assertions
to verify event payloads structurally, and stop miscategorizing
dispatch errors as CopilotKitMisuseError (preserve original stack).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback on copilotkit_emit_tool_call:
- Add non-empty string validation for the tool call ID in all 3 SDKs
- Rename Python `id` param to `tool_call_id` to avoid shadowing the builtin
- Refactor JS 4th positional arg to options bag `{ id?: string }` for extensibility
- Document that the ID is also used as parentMessageId in AG-UI events
- Add JS tests for the new parameter (generated ID, custom ID, validation)
- Add Python validation tests (empty string, whitespace rejection)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow callers to supply a custom tool call ID for correlation,
idempotency, and observability. Falls back to uuid4 when omitted.
Applied consistently across Python LangGraph, Python CrewAI, and JS SDK.
Also returns the tool call ID from all variants for downstream reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The release-PR workflow's pre-commit hook ran the full test suite and
failed because @tanstack/virtual-core 3.13.18 has a latent bug — its
scrollToIndex schedules a nested rAF that calls
`this.targetWindow.requestAnimationFrame(verify)` with no null-check.
The virtualizer's cleanup nulls `targetWindow` on React unmount, so the
queued rAF fires post-unmount and throws. All 1170 tests passed, but
vitest exited non-zero from the unhandled error.
Wrap rAF on both globalThis and window (separate bindings in
vitest+jsdom; tanstack uses `targetWindow.rAF` which resolves to
`window.rAF`) so callbacks hitting this specific error are swallowed.
Also fix the lint-fix lefthook command — `[ -n "{staged_files}" ]`
broke on multi-file expansion ("[: <path>: unexpected operator")
because lefthook interpolates files as space-separated words, not a
quoted string. Use `set --` to put them in positional args.
## Summary
Two small dojo design tweaks, plus an unrelated test fix required to
commit.
**Dojo sidebar — categories as headers**
(`showcase/shell-dojo/src/app/page.tsx`)
- Replace the single static "Demos" label with the feature category
names already present in the registry, so navigation reflects how demos
are grouped instead of presenting one flat list (Dev Ex, Chat & UI,
Platform, Controlled Generative UI, …).
- Bump shared `SectionTitle` to 11px / weight 600 / primary text color
for a touch more prominence. Affects the "Integrations" and "View"
headers above as well — consistent treatment across all sidebar
sections.
**Built-in agent name**
(`showcase/integrations/built-in-agent/manifest.yaml`)
- Renamed "Built-in Agent (TanStack AI)" → "CopilotKit Built-in Agent"
in both display name and description, so users aren't pushed to think
about the LLM backend in the integration name. (Note: the internal
source file `src/lib/factory/tanstack-factory.ts` is unchanged — that's
a real filename surfaced in the code viewer; renaming it is a bigger
refactor.)
**Test fix — vitest localStorage polyfill** (`packages/web-inspector/`)
- Pre-existing failure on `main`: Node 22.4+ ships an experimental
built-in `localStorage` global that is installed before vitest's jsdom
env runs, leaving `window.localStorage` as an uninitialized stub. All 22
telemetry persistence tests crash with `window.localStorage.clear is not
a function` on Node 25.
- Added `vitest.setup.ts` that installs a minimal in-memory `Storage`
shim on `globalThis.{localStorage,sessionStorage}`. Works on Node
18/20/22/25+ without depending on `--no-experimental-webstorage`. All 29
tests now pass on Node 25.
- This is in its own commit (`fix(web-inspector): …`) so it's easy to
cherry-pick out if you'd prefer it as a separate PR.
## Test plan
- [ ] Visual review of dojo sidebar at `localhost:3001` — categories
render as headers above their demos
- [ ] Built-in agent appears as "CopilotKit Built-in Agent" in the
integration dropdown
- [ ] `pnpm nx run @copilotkit/web-inspector:test` passes on Node 25
- [ ] Full lefthook pre-commit passes (already verified locally on both
commits)
🤖 Generated with [Claude Code](https://claude.com/claude-code)