95 Commits

Author SHA1 Message Date
Maxim be9b60c8a9 fix(sdk): harden AG-UI dispatch, add exception hierarchy, fix docstrings
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>
2026-05-20 00:18:22 +02:00
Maxim 9cb8996056 fix(sdk): harden validation, error types, and dispatch safety across 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>
2026-05-20 00:18:21 +02:00
github-actions[bot] 6fb417a90a style: auto-fix formatting 2026-05-20 00:18:21 +02:00
Maxim 50301b7bf0 fix(sdk): restore error type, rename options.id, add validation parity
- 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>
2026-05-20 00:18:21 +02:00
Maxim 09ede29d58 fix(sdk): align whitespace validation, improve error handling and docs
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>
2026-05-20 00:18:20 +02:00
github-actions[bot] d2070b7b5f style: auto-fix formatting 2026-05-20 00:18:20 +02:00
Maxim c7693e938e fix(sdk): validate id param, rename to tool_call_id, use options bag 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>
2026-05-20 00:18:20 +02:00
github-actions[bot] 174be4f355 style: auto-fix formatting 2026-05-20 00:18:19 +02:00
Maxim 865991b463 feat(sdk): add optional id parameter to copilotkit_emit_tool_call
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>
2026-05-20 00:18:19 +02:00
Jordan Ritter 2482317ccc style: apply ruff format to Python codebase
320 files reformatted. One-time alignment to match the ruff format
check added to CI in #4812.
2026-05-13 23:10:35 -07:00
Jordan Ritter a260ffe106 feat: widen header forwarding from x-aimock-* to all x-* headers
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.
2026-05-11 20:55:33 -07:00
Jordan Ritter fce31b5079 feat(sdk-python): add header propagation for X-AIMock-Strict
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.
2026-05-11 13:36:55 -07:00
Ran Shem Tov b3a5fe2670 chore: fix pytests 2026-04-28 15:16:25 +02:00
Ran Shem Tov 6c14258a39 feat(langgraph): add state injection to copilotkit middleware 2026-04-28 13:24:41 +02:00
Martha Schumann 505bc9baef test(sdk-python): remove empty-string delta test
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>
2026-04-22 10:03:46 -07:00
Martha Schumann 96e8112039 Merge remote-tracking branch 'origin/main' into claude/langgraph-tests-cleanup-1jVZ3 2026-04-22 09:48:55 -07:00
Martha Schumann 6e7c7bbc53 test(sdk-python): remove orphaned tests for deleted langgraph_agent module
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>
2026-04-22 09:46:53 -07:00
Alem Tuzlak ae1219455a Merge branch 'main' into fix/crewai-import-compat-3268 2026-04-22 12:58:24 +02:00
Alem Tuzlak b8349d2067 fix(sdk-python): copilotkit_interrupt handles non-list resume values (#3096) (#3784)
## 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
2026-04-22 12:33:27 +02:00
Alem Tuzlak d6ff5772ed fix(sdk-python): LangGraphAGUIAgent serializes Context objects to dicts (#3690) (#3787)
## 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
2026-04-22 12:33:14 +02:00
Alem Tuzlak 95fe4889b6 Merge remote-tracking branch 'origin/main' into fix/crewai-import-compat-3268
# Conflicts:
#	sdk-python/pyproject.toml
2026-04-22 12:26:49 +02:00
Martha Schumann 0e77affe14 Merge main: remove langgraph_agent.py (deprecated), resolve lock conflict
- 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>
2026-04-20 12:10:19 -07:00
Maxim 45b28dd3b3 fix(sdk-python): update test to match always-emit assistant message behavior
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>
2026-04-17 15:02:31 +02:00
Maxim 85dbf25c74 Merge branch 'main' into fix/always-emit-assistant-message 2026-04-17 14:42:22 +02:00
Jordan Ritter 19798bbbd3 fix(sdk-python): support both old and new crewai import paths (#3268) 2026-04-14 16:24:09 -07:00
Jordan Ritter e01d510a7a fix(sdk-python): serialize Pydantic Context and Tool objects to dicts in LangGraphAGUIAgent (#3690) 2026-04-14 16:23:52 -07:00
Jordan Ritter de78dfdb68 fix: sanitize NaN/Infinity values in LangGraph state before JSON serialization (#3851)
## 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*
2026-04-14 14:06:07 -07:00
Jordan Ritter fe365d3f7f fix: extract all text parts when AIMessage content is a list (#3844)
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.
2026-04-14 14:06:04 -07:00
Jordan Ritter 76b1d73bb8 fix: convert Pydantic BaseModel instances to dicts before serialization (#2158) (#3816)
## 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
2026-04-14 14:06:00 -07:00
Maxim b82e13d134 test(sdk-python): extract shared helpers and add missing edge case test
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>
2026-04-14 20:18:11 +02:00
Maxim 25cc48be27 test(sdk-python): add tests for crewai assistant message emission
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>
2026-04-14 19:45:23 +02:00
Maxim 54eed60971 fix(sdk-python): emit assistant message in crewai + review improvements
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>
2026-04-14 19:34:39 +02:00
Maxim d99afc3759 test(sdk-python): add tests for assistant message emission with tool calls
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>
2026-04-14 17:40:33 +02:00
Jordan Ritter 6d9efa00a9 test: add comprehensive tests for _sanitize_for_json
27 tests covering:
- Passthrough of valid types (floats, ints, strings, booleans, None)
- NaN and +/-Infinity replacement with None
- Recursive sanitization through dicts, lists, and tuples
- Real-world event structures (on_chain_end, streaming chunks, state sync)
2026-04-13 08:30:07 -07:00
Jordan Ritter e76b7f435e test(sdk-python): replace mock test with integration tests for emit_state merge
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
2026-04-13 08:29:15 -07:00
Jordan Ritter a559406bee fix: also serialize Pydantic state in get_state() and fix implicit None return
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).
2026-04-13 08:24:10 -07:00
Jordan Ritter 2e92fe1bff test: add tests for multi-part AIMessage content extraction
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.
2026-04-13 08:19:29 -07:00
Jordan Ritter fee1a888b2 fix(sdk-python): copilotkit_interrupt handles non-list resume values (#3096)
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.
2026-04-12 13:22:28 -07:00
Jordan Ritter ee9e9c37b8 fix(sdk-python): copilotkit_emit_state merges state instead of replacing (#3138)
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.
2026-04-12 13:21:53 -07:00
Martha Schumann 21d2051b33 Close three PR review blockers: cross-layer event tests, API removals verified
- 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>
2026-04-10 17:18:42 -07:00
Martha Schumann a90dbce49d test(sdk-python): add edge case coverage for emit filtering and merge state
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>
2026-04-10 13:11:41 -07:00
Claude 2171c7b855 test: address review feedback on LangGraph tests
- Python: extract track_parent_dispatches() context manager, eliminating
  repeated boilerplate; import AGUIBase directly instead of __bases__[0]
- Python: use named constants for "copilotkit_exit" and "Exit" strings
  with comments explaining why they're literals (source uses them too)
- TS runtime: extract withMockedParentMerge() helper using vi.spyOn for
  automatic cleanup instead of manual try/finally prototype patching
- TS runtime: add makeCustomEvent() factory to centralise as-any casts
- TS runtime: use CustomEventNames constants for all custom event tests
- TS runtime: fix misleading comment on empty string content test
- TS runtime: add callPrivate() docstring noting the any trade-off

https://claude.ai/code/session_01BPMn7zhadhapfyyD8kYeAH
2026-04-03 23:12:52 +00:00
Claude d849fae68b test: add LangGraph integration unit tests + dead code annotations
- 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
2026-04-03 22:44:09 +00:00
Jordan Ritter 8bad9f30ac test: add unit tests for emit_messages/emit_tool_calls filtering
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.
2026-03-06 08:32:06 -08:00
Ariel Weinberger cd8ff1bdc6 feat(python-sdk): add to monorepo (#908) 2024-11-12 18:59:33 -06:00