## Summary
Moves `@copilotkit/runtime` from `dependencies` to `peerDependencies` in
`@copilotkit/voice`, and adds it to `devDependencies` so voice's own
typecheck/tests/build continue to work.
## Why
When voice declares runtime as a regular dependency, npm/pnpm may
install a **second copy** of `@copilotkit/runtime` nested under
`node_modules/@copilotkit/voice/node_modules/` whenever the resolved
version differs from the consumer's top-level runtime version. This
causes:
- **Type drift** — consumers importing from voice get types/classes from
the nested runtime, while their app code uses the top-level runtime.
Identity checks fail, type assertions silently degrade.
- **Singleton drift** — module-level state (caches, registries)
duplicates across the two copies.
- **Version slip** — patch updates to the top-level runtime don't
propagate to the nested copy.
As a peer dependency, voice now defers entirely to the consumer's
runtime version. The `devDependencies` entry keeps voice's own
typecheck/tests/build green.
## Consumer audit (CK monorepo)
Every package/example/showcase integration that depends on
`@copilotkit/voice` also declares a direct dependency on
`@copilotkit/runtime` — verified across all 21 consumers (19 showcase
integrations + 2 v2 examples). No consumer is broken by this move.
## Changes
- `packages/voice/package.json`: runtime moved `dependencies` →
`peerDependencies` + added to `devDependencies` (workspace:* in all
three blocks where applicable, matching the existing pin)
- `pnpm-lock.yaml`: regenerated, scoped to the voice importer block only
(3 lines moved between dependencies/devDependencies)
## Verification
- `pnpm install --lockfile-only` succeeds with clean, scoped diff
- `pnpm check-types` in voice produces **zero new errors** vs `main`
(the 2 pre-existing errors — module-resolution +
`@copilotkit/runtime/v2` typing — are unchanged baseline)
- Per-package `pnpm --filter @copilotkit/voice test` passes (1/1)
## Notes on commit hook
- Skipped `lint-fix` (oxfmt rewrites `package.json` to JSON5 syntax with
trailing commas, producing invalid JSON that breaks `pnpm install` —
pre-existing bug unrelated to this change; sibling `package.json` files
have not been touched by it)
- Skipped `test-and-check-packages` (pre-existing
`@copilotkit/web-inspector:test` failure on `main`: `TypeError:
window.localStorage.clear is not a function` — reproduced on `main`
HEAD, unrelated to voice)
Prevents nested-deps drift when consumers depend on @copilotkit/runtime
at a different version than voice's pinned version. Runtime is now a
peer dependency (consumer-controlled), with devDependencies retaining
the pin so voice's own tests and typecheck continue to work.
Picks up the forwarded-headers fix from ag-ui PR #1798
(https://github.com/ag-ui-protocol/ag-ui/pull/1798), which injects
agent.headers as config.configurable.copilotkit_forwarded_headers so
the LG dev server's HTTP-to-configurable bridge is no longer required
for X-AIMock-Context propagation. Closes the header-propagation gap
for showcase D5/D6 langgraph-typescript probes.
Node 25 ships an experimental built-in localStorage global accessor that
shadows jsdom's mock, leaving window.localStorage as an empty stub with
no clear/setItem/removeItem/getItem methods. This broke all 22 telemetry
tests with 'window.localStorage.clear is not a function' and was
blocking pre-commit hooks repo-wide.
Add a vitest setup file that installs a proper in-memory Storage shim
on both globalThis and window before each test, so jsdom-environment
tests behave the same on Node 20 and Node 25.
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>