Commit Graph

86 Commits

Author SHA1 Message Date
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