Skip to content

[pull] main from CopilotKit:main#292

Merged
pull[bot] merged 40 commits into
TheTechOddBug:mainfrom
CopilotKit:main
May 27, 2026
Merged

[pull] main from CopilotKit:main#292
pull[bot] merged 40 commits into
TheTechOddBug:mainfrom
CopilotKit:main

Conversation

@pull

@pull pull Bot commented May 27, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

mxmzb and others added 30 commits May 20, 2026 00:18
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>
…in JS

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>
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>
- 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>
…SDKs

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>
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>
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 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>
… args validation

- 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>
… validation order

- 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>
…, use UUID for tool call IDs

- 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>
… flag ordering

The split queue_put calls introduced interleaving risk (6 yield points
vs 2) and the end_attempted flag was set before the END dispatch,
causing the compensating END to be skipped when END itself failed.

- CrewAI: restore single batched queue_put(start, args, end) call;
  compensating END is now unconditional on batch failure
- AG-UI agent: rename end_attempted → end_dispatched, set after
  successful END dispatch so compensation fires for all failure modes
- Tests: rewrite CrewAI compensating tests for batch semantics,
  add test_failure_on_end_emits_compensating_end for AG-UI agent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… in sdk.py

Remove no-op `try/except CancelledError: raise` around asyncio.shield
in copilotkit_emit_message (the shield handles cancellation on its own).
Remove unused CopilotKitError and CopilotKitMisuseError imports from
sdk.py (already re-exported via __init__.py).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tracts

The AG-UI dispatcher's ManuallyEmitToolCall handler rejected non-dict,
non-string args with CopilotKitMisuseError, but all three emitters
(JS, Python LangGraph, Python CrewAI) accept any JSON-serializable
value. This mismatch caused JS-emitted list/number args to crash the
Python dispatcher.

Replace the strict isinstance(dict, str) check with a None guard and
rely on the existing json.dumps try/except for serializability.

Call-site enumeration:
- langgraph_agui_agent.py:129 — changed from isinstance check to None guard
- test_emit_tool_call_optional_id.py — updated test_missing_args_raises match,
  test_non_serializable_args_raises match, converted test_list_args_raises and
  test_int_args_raises from negative to positive tests
- No other call sites reference the removed isinstance pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new test_emit_tool_call_optional_id.py uses async test methods
decorated with @pytest.mark.asyncio, but pytest-asyncio was missing
from dev dependencies — causing all 11 async tests to fail in CI
across all Python versions (3.10–3.14).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

## Summary
- Adds an optional `tool_call_id` / `toolCallId` parameter to
`copilotkit_emit_tool_call` across all three SDK variants (Python
LangGraph, Python CrewAI, JS/TS)
- When provided, the custom ID is used as the `toolCallId` in AG-UI
protocol events; when omitted, a random UUID is generated (backwards
compatible)
- All variants now return the tool call ID (`str` / `Promise<string>`)
instead of `True` / `void`, aligning the LangGraph Python and JS
variants with CrewAI which already returned it
- Introduces `CopilotKitError` base exception class and
`CopilotKitMisuseError` for invalid API usage in the Python SDK
- Adds compensating `TOOL_CALL_END` / `action_execution_end` dispatch
when mid-stream failures occur, preventing stuck client UIs
- Exports all exception types from the `copilotkit` package root

## Motivation
Customer request — enables use cases including:
- **Correlation across agent steps** — emit a tool call with a known ID
and reference it later
- **Idempotency / replay safety** — deterministic IDs prevent duplicate
tool calls on LangGraph checkpoint replays
- **External system linking** — use a job ID, trace span ID, or request
correlation ID as the tool call ID
- **Custom HITL flows** — programmatically approve/reject a specific
tool call by predictable ID
- **Testing** — deterministic IDs make snapshot/integration tests stable

## Behavioral changes
- **JS dispatch errors are no longer wrapped in
`CopilotKitMisuseError`**: Previously, `copilotkitEmitToolCall` wrapped
any `dispatchCustomEvent` failure in a `CopilotKitMisuseError`.
Transport/dispatch errors now propagate as their native error types.
This is more correct — a dispatch failure is not a misuse error — but
callers that specifically caught `CopilotKitMisuseError` for transport
errors will need to update their catch clauses.
- **Python return type changed**: `copilotkit_emit_tool_call` previously
returned `True` (documented as `Awaitable[bool]`). It now returns `str`
(the tool call ID). Code using truthiness checks (`if result:`) is
unaffected; strict equality (`== True`) will break.

## Test plan
- [x] 4 unit tests for LangGraph Python variant (default UUID, custom
ID, return value, explicit None)
- [x] 3 unit tests for CrewAI Python variant (skipped when crewai not
installed)
- [x] 3 integration tests for AG-UI dispatch (custom ID propagates to
all TOOL_CALL events)
- [x] All 26 existing `test_agui_agent.py` tests pass (no regressions)
- [x] JS SDK builds cleanly (`nx run @copilotkit/sdk-js:build`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…page

- Rewrites the root build-with-agents page to document Skills + MCP Docs Server
- Skills section: intro, npx skills add command, skills table, starter prompt
- Removes hideTOC so the new TOC (Skills / MCP Docs Server) renders
- Updates icon to BrainCircuit and description to keyword-rich copy
- Renames mcp-server-setup snippet heading: ## Overview → ## MCP Docs Server
  so both sections compose cleanly on the root page without a double Overview

Closes out the last AC on OSS-133: npx skills add flow documented with
table of skills, starter prompt, and page structure matching Mastra reference.
The table was incomplete (6 skills listed, 12 actually install) and has
no CI check to keep it in sync. Replace with a one-line prose summary
and a link to the canonical skills/ directory on GitHub, which is always
accurate.
Add `metadata.internal: true` so the skill is hidden from
`npx skills add CopilotKit/CopilotKit` public installs.
Same pattern as PR #4937 for git-hooks and copilotkit-demo-parity.
built-in-agent uses docs_mode: 'authored' so it loads the per-framework
integrations/built-in-agent/build-with-agents.mdx directly, bypassing
the root page entirely. Other frameworks (generated mode) fall through
to the root page and already show the skills section.

Fix: extract the full page body into a shared snippet
  snippets/shared/guides/build-with-agents.mdx
register it as <BuildWithAgents /> in SNIPPET_MAP + mdx-registry, and
switch built-in-agent's page to <BuildWithAgents />. Root page keeps
its inline content (for crawlability). The snippet is the source used
by authored-mode frameworks; root is the source for generated-mode and
crawlers — same pattern as MCPSetup / coding-agents.
onsclom and others added 10 commits May 27, 2026 09:13
…ntent

Root page was duplicating the snippet content inline. Switch it to
<BuildWithAgents /> so snippets/shared/guides/build-with-agents.mdx
is the one source — root page and built-in-agent both render from it.
## Summary

Adds `metadata.internal: true` to
`.claude/skills/showcase-demo-debugging/SKILL.md` so it is hidden from
`npx skills add CopilotKit/CopilotKit` public installs.

## Why

`showcase-demo-debugging` is a contributor-only skill (for working on
showcase demos, fixtures, and CI regressions). It has no value to a user
building a CopilotKit app, but was showing up in the install summary.
This brings it in line with `git-hooks` and `copilotkit-demo-parity`
which were fixed the same way in PR #4937.

## Change

```yaml
metadata:
  internal: true
```

One field, same pattern as #4937.
…page (#5039)

## Summary

Adds the Skills installation section to the shell-docs
`/build-with-agents` page

## Changes


**`showcase/shell-docs/src/content/snippets/shared/guides/build-with-agents.mdx`**
(new)
- Single source of truth for the page content
- Intro paragraph, `## CopilotKit Skills` section (Callout + Steps:
install + starter prompt), `<MCPSetup />` as second section

**`showcase/shell-docs/src/content/docs/build-with-agents.mdx`**
- Replaced inline content with `<BuildWithAgents />` — delegates to the
shared snippet
- Icon updated to `BrainCircuit`, description updated to keyword-rich
copy, `hideTOC: true` removed


**`showcase/shell-docs/src/content/docs/integrations/built-in-agent/build-with-agents.mdx`**
- Replaced `<SharedContent />` with `<BuildWithAgents />` so the skills
section appears here too (built-in-agent uses `docs_mode: authored` and
never falls through to the root page)


**`showcase/shell-docs/src/content/snippets/shared/guides/mcp-server-setup.mdx`**
- `## Overview` → `## MCP Docs Server`

**`showcase/shell-docs/src/lib/docs-render.tsx`** +
**`mdx-registry.tsx`**
- Register `BuildWithAgents` in `SNIPPET_MAP` and component registry

## Acceptance criteria status

- [x] `/coding-agents` redirects to `/build-with-agents` (PR #5023)
- [x] React Native moved to Platforms section (PR #4927 / #5023)
- [x] `npx skills add` flow documented with skills link and starter
prompt
- [x] MCP server install instructions present (existing content, now
labeled correctly)
- [x] Page structure follows Mastra "Build with AI" reference
)

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>
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>
) (#5042)

## Summary

- Wires `useAgent` to sync `agent.threadId` from
`CopilotChatConfigurationProvider` when the caller marked the threadId
as explicit. AbstractAgent's constructor was auto-minting a UUID, so
`ProxiedCopilotRuntimeAgent` was shipping that random UUID in
`/agent/run`, `/agent/connect`, `/agent/stop` instead of the value the
caller passed via `<CopilotKit threadId={x}>` — breaking thread
persistence and causing 404s on lookup.
- Fixes #5041 (V1 `<CopilotChat>` path) and #4739 (headless `useAgent`
path), which share the same root cause.

## Why this regressed

PR #3525 originally solved this with per-thread agent cloning. That
cloning was reverted in commit `762370a4e5` (May 2026) because it wiped
state on tool calls. The revert restored the explicit `agent.threadId =
resolvedThreadId` assignment only in the V2 `CopilotChat` component —
the V1 chat hook (`use-copilot-chat_internal.ts`) and headless
`useAgent` paths were left without an equivalent sync.
`use-agent-thread-isolation.test.tsx` (333 lines) was deleted in the
revert and never replaced for those surfaces, which is why this
regressed silently.

The fix lives in `useAgent` rather than the V1 chat hook because V1 chat
already routes through `useAgent` for agent acquisition, and a headless
`useAgent` user (issue #4739) gets the sync for free. The gate on
`hasExplicitThreadId` matches V2 `CopilotChat`'s gating so a
`ThreadsProvider`-minted placeholder doesn't overwrite the agent's
auto-minted UUID before a real thread exists.

## Test plan

- [x] 4 new unit tests in `use-agent-threadid-sync.test.tsx` covering:
explicit sync, the non-explicit no-op path, threadId change re-sync, and
the no-provider headless case.
- [x] 2 new integration tests in `v1-explicit-threadid-bridge.test.tsx`
covering the customer's exact scenario: `<CopilotKit threadId={x}>` →
`useAgent` → `agent.threadId === x`.
- [x] All 1177 existing `@copilotkit/react-core` tests still pass.
- [x] `@copilotkit/react-core:build` succeeds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Reworked the search modal into a clearer docs-scoped search experience
with a visible "Searching docs for" framework selector
- Removed demo results from search and improved framework-specific
deduping for docs content
- Tightened the homepage and header chrome spacing to better fit the
updated shell-docs theme
- Ensured the CopilotKit kite mark is used consistently for the built-in
agent selector

## Testing
- `npm run typecheck` passed
- `npm run lint` passed with existing repository warnings only
- Verified the updated search modal and selector behavior in the local
browser
The prerelease workflow has been OOMing on Upload workspace since the
build/publish split in 770759a. The artifact upload enumerates
~6M files (root + per-package node_modules with pnpm symlinks all
materialized, plus build caches) and actions/upload-artifact builds
the full manifest in memory before streaming, blowing past the 4GB
Node heap limit.

publish-release.yml already had the working pattern: exclude
node_modules/.next/.turbo/.nx and re-run pnpm install --frozen-lockfile
in the publish job. Port it over so prerelease publishes succeed
again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary

- The `release / pre` workflow has been failing on every run since
commit 770759a ("fix(ci): separate build and publish jobs in release
workflows", May 11). The `Upload workspace` step hits a JS heap OOM in
`actions/upload-artifact@v7` after enumerating 5,923,811 files.
- Root cause: the build → publish handoff uses `path: .` +
`include-hidden-files: true`, which packs the entire workspace **after
`pnpm install`** — including every `node_modules/`, every nested
`.pnpm/` store entry (symlinks materialized), `.next`, `.turbo`, `.nx`
caches, plus every `examples/*/node_modules` and showcase app subtree.
The action holds the full manifest in memory before streaming → ~4GB
heap, OOM.
- `publish-release.yml` already encountered this and was patched: it
excludes `node_modules`/`.next`/`.turbo`/`.nx` and re-runs `pnpm install
--frozen-lockfile` in the publish job. This PR ports the same pattern to
`prerelease.yml` — no novel design.

## Why this preserves the security split

The original 770759a change separated `build` and `publish` so
`NPM_TOKEN` is only in the publish job's process tree, preventing token
exfiltration from build-time code. That property is untouched: the
artifact still carries pre-built `dist/` directories with the
already-bumped versions, and the publish job still runs only the publish
script. The `pnpm install --frozen-lockfile` in the publish job runs
against the same `pnpm-lock.yaml` carried in the artifact —
deterministic and at the same versions the build job validated.

## Test plan

- [ ] Trigger a `release / pre` run with `--dry-run=true` against this
branch — expect `Upload workspace` to complete in seconds instead of
OOMing after 11 minutes.
- [ ] Verify the `Publish prerelease` step still finds `tsx` and `pnpm
pack` works (i.e. the added `Install Dependencies` step restores
node_modules correctly).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@pull pull Bot locked and limited conversation to collaborators May 27, 2026
@pull pull Bot added the ⤵️ pull label May 27, 2026
@pull pull Bot merged commit ee6b2c3 into TheTechOddBug:main May 27, 2026
0 of 13 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants