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>
Matches the CopilotKit runtime's extractForwardableHeaders() which
already forwards all x-* prefixed headers. Enables any custom
x-* header to propagate from browser through AG-UI to LLM calls.
ContextVar-based ambient state + httpx event hook. Incoming
x-aimock-* headers from AG-UI requests are forwarded to outgoing
LLM API calls. Keys normalized to lowercase. Warning emitted when
install_httpx_hook receives an unrecognized client type.
ag-ui-protocol 0.1.15 added a validator requiring TextMessageContentEvent.delta
to have length >= 1. The test asserted that manually_emit_message with an empty
string still emits the full TEXT_MESSAGE_START/CONTENT/END sequence — that
behavior is no longer achievable at the protocol layer, and emitting empty
content events was never semantically useful. Removing rather than guarding
in code: the protocol should fail loudly on empty deltas, not silently drop
them in CopilotKit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The main-merge commit (0e77affe1) removed sdk-python/copilotkit/langgraph_agent.py
as deprecated, but three test files still imported private helpers from it:
- test_emit_state_merge.py (_merge_emit_state)
- test_pydantic_state_serialization.py (LangGraphAgent, _serialize_state)
- test_sanitize_for_json.py (_sanitize_for_json)
Collection failed with ModuleNotFoundError, so the python-sdk unit job failed.
Behavior these tests covered now lives in the ag_ui_langgraph PyPI package
(external dep), not in this repo — so there is nothing to port.
Also fix test_emit_filtering.py::test_run_filters_none_events: replace
asyncio.get_event_loop().run_until_complete(...) with asyncio.run(...) —
get_event_loop() raises RuntimeError in Python 3.12 when no loop is current.
The failure was masked in CI because collection errored out before this test ran.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
- `copilotkit_interrupt` now handles string and dict resume values from
LangGraph 1.x's `interrupt()`
- Previously crashed with `AttributeError: 'str' object has no attribute
'content'` or `KeyError: -1`
- Type-checks response: str returned directly, dict JSON-serialized,
list uses existing `[-1].content` path
## Test plan
- [x] Red-green test: string resume value returns without crash
- [x] Red-green test: dict resume value returns JSON string
- [x] Test: list resume value still works (existing behavior)
- [x] Full test suite passes (15/15)
Closes#3096
## Summary
- `LangGraphAGUIAgent.langgraph_default_merge_state` now calls
`model_dump()` on Pydantic Context objects before storing in copilotkit
state
- Previously stored raw Pydantic objects, causing JSON serialization
failures downstream
- Handles mixed types: Pydantic objects get `model_dump()`, plain dicts
pass through unchanged
- Matches the existing pattern already used in `CopilotKitMiddleware`
## Test plan
- [x] Red-green test: AG-UI Context objects stored as plain dicts, not
Pydantic
- [x] Red-green test: mixed Pydantic + dict context items all
serializable
- [x] Full test suite passes (15/15)
Closes#3690
- Keep deletion of sdk-python/copilotkit/langgraph_agent.py (deprecated LangGraphAgent
removed in this PR; main's unrelated bug fixes are superseded by our removal)
- Resolve poetry.lock conflict by taking main's ag_ui_langgraph 0.0.33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The test_empty_list_returns_empty_content test expected empty-content
AIMessages to be filtered out, but the fix now always emits assistant
messages so tool calls can reference them via parentMessageId.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
- Add sanitization pass over LangGraph agent state to replace NaN and
Infinity with null before JSON serialization
- Prevents `ValueError: Out of range float values are not JSON
compliant` in Python SDK
Closes#1955
---
*Split from #3847*
Partially addresses #1748
When Anthropic models return multi-part content lists, only the first
element was used and the rest discarded. Now iterates all parts and
concatenates text blocks, preserving the full message content.
Split from #3838.
## Summary
Fixes#2158
Pydantic `BaseModel` instances in LangGraph agent state are not
serializable by `langchain_dumps`. This adds a recursive
`_serialize_state` helper that converts `BaseModel` instances to dicts
before serialization, preventing crashes when state contains Pydantic
models.
**Additional fixes (second commit):**
- Also applies `_serialize_state` to the `get_state()` code path, which
was missed in the original fix but has the same bug
- Fixes `filter_state_on_schema_keys` returning `None` implicitly when
schema keys are not set (the `except` branch returned `state` but the
non-matching `if` branch did not)
- Adds 17 tests covering `_serialize_state`, `_emit_state_sync_event`,
and `get_state` with Pydantic models
## Merge order note
This PR and #3851 both modify
`sdk-python/copilotkit/langgraph_agent.py`. Both add a helper function
at module level and call it from `_emit_state_sync_event` and
`get_state`. Whichever merges second will need a trivial rebase. No
semantic conflict — the fixes are complementary (this one handles
Pydantic models, #3851 handles NaN/Infinity).
## Test plan
- [x] 17 unit tests covering both code paths
- [x] Red-green verified: `get_state` tests fail without fix, pass with
it
- [x] Existing test suite (test_emit_filtering) still passes
- [x] Verify LangGraph agent with Pydantic BaseModel state serializes
correctly
- [x] Verify non-Pydantic state is unaffected
Extract _convert_and_split() helper in both test files to eliminate
duplicated filtering logic across tests. Add test for tool_call
without id being silently skipped (crewai tc_id is None guard).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add test suite for crewai_flow_messages_to_copilotkit covering the same
parentMessageId orphan scenarios as the langgraph tests: function-style and
direct-style tool calls with empty/missing content, orphan detection, and
plain messages. Also fix pre-existing KeyError in the name extraction loop
which only handled function-style tool calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply the same parentMessageId orphan fix to crewai_flow_messages_to_copilotkit
where the elif chain meant tool-call messages never emitted the parent assistant
message. Also refine langgraph fix: use explicit None check instead of truthiness,
add inline comments explaining the invariant, remove unused pytest import,
replace fragile commit hash in docstring, and add test for list-type content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies that langchain_messages_to_copilotkit always emits the
assistant message even when content is empty (OpenAI-style tool-call-only
responses), ensuring no orphaned parentMessageId references.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The original test only tested the _merge_emit_state helper in isolation
using mocks. Replace with integration tests that simulate the actual
state-tracking loop from _stream_events, including:
- Sequential emits with different keys preserve all keys (core bug)
- Proof that the bug manifests without current_graph_state.update
- Three sequential emits accumulate correctly
- Same key emitted twice uses latest value
- Non-dict emit does not corrupt current_graph_state
- Initial state reference is not mutated
The original fix only covered _emit_state_sync_event but missed the
get_state() code path, which also returns state containing Pydantic
BaseModel instances to callers that will JSON-serialize downstream.
Also fixes filter_state_on_schema_keys returning None implicitly when
schema keys are not set (the except branch returned state but the
non-matching if branch did not).
Adds 17 tests covering _serialize_state, _emit_state_sync_event, and
get_state with Pydantic models (nested, lists, plain dicts, empty).
Verifies langchain_messages_to_copilotkit correctly concatenates all text
parts from list-style content blocks (Anthropic models). Includes
regression tests for the exact scenario from #1748 (text + image blocks)
and tests for string lists, mixed content, empty lists, and edge cases.
LangGraph 1.x can return string or dict resume values from interrupt(),
not just lists. The code now type-checks the response: strings are
returned directly, dicts are JSON-serialized, and lists use the
existing [-1].content path.
Sequential emit_state calls with different keys now preserve all keys
in the snapshot. Previously, each call would replace the entire
manually_emitted_state, losing keys from earlier calls.
- Add TestAGUIStyleEventIntegration to test_agui_agent.py: proves that AG-UI-style
unprefixed events (manually_emit_message, manually_emit_tool_call, exit) flow
correctly through CopilotKit's _handle_single_event to the AG-UI base handler
without being suppressed or double-converted by the CopilotKit layer.
- Replace LangGraphAgent with LangGraphAGUIAgent in both example files
(canvas/gemini and v1/_legacy/saas-dynamic-dashboards) to close the concern
about removed APIs still being referenced externally.
- Update poetry.lock to reflect ag_ui_langgraph 0.0.32 and dependency updates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expands test suite for LangGraphAGUIAgent:
- TestLanggraphDefaultMergeState: verify no duplicates in copilotkit.actions
and that tool ordering is preserved in the merged result
- TestReasoningContentPreservation: verify unknown custom events pass through,
empty-string messages still emit the full TEXT_MESSAGE_* sequence, and
empty-args tool calls still emit the full TOOL_CALL_* sequence
Expands test suite for emit filtering:
- TestMissingOrNoneRawEvent: events with rawEvent=None should pass through
to super() without crashing
- TestNoneEmitMetadataValues: None and 0 values for emit metadata keys
should NOT filter (only exactly False suppresses events)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Python SDK: 18 new tests for LangGraphAGUIAgent (custom event handling,
emit filtering, state merging, copilotkit namespace)
- TypeScript SDK: 25 new tests for copilotkitCustomizeConfig and
convertActionsToDynamicStructuredTools
- TypeScript Runtime: 27 new tests for event-source helpers
(shouldEmitToolCall, getCurrentMessageId, getCurrentContent, etc.)
- TypeScript Runtime: expanded dispatch-event-filtering tests with
custom event dispatch (manually_emit_message/tool_call/state, exit)
and langGraphDefaultMergeState tests
- Dead code annotations: LangGraphAgent class and use_function_call=True
branch annotated with TODO(ran-review) for Ran to verify
https://claude.ai/code/session_01BPMn7zhadhapfyyD8kYeAH
Tests the two bugs fixed in this PR:
1. Dict raw_event metadata must be read with .get(), not getattr()
2. Filtered events must return None (not ""), and run() strips them
Adds pytest as a dev dependency.