copilotkit 0.1.86__tar.gz → 0.1.87__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {copilotkit-0.1.86 → copilotkit-0.1.87}/PKG-INFO +3 -3
  2. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/a2ui.py +33 -42
  3. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph.py +12 -2
  4. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph_agent.py +58 -6
  5. {copilotkit-0.1.86 → copilotkit-0.1.87}/pyproject.toml +1 -1
  6. {copilotkit-0.1.86 → copilotkit-0.1.87}/README.md +0 -0
  7. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/__init__.py +0 -0
  8. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/action.py +0 -0
  9. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/agent.py +0 -0
  10. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/copilotkit_lg_middleware.py +0 -0
  11. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/__init__.py +0 -0
  12. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/copilotkit_integration.py +0 -0
  13. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/crewai_agent.py +0 -0
  14. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/crewai_sdk.py +0 -0
  15. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/exc.py +0 -0
  16. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/html.py +0 -0
  17. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/integrations/__init__.py +0 -0
  18. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/integrations/fastapi.py +0 -0
  19. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langchain.py +0 -0
  20. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph_agui_agent.py +0 -0
  21. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/logging.py +0 -0
  22. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/parameter.py +0 -0
  23. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/protocol.py +0 -0
  24. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/runloop.py +0 -0
  25. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/sdk.py +0 -0
  26. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/types.py +0 -0
  27. {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/utils.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: copilotkit
3
- Version: 0.1.86
3
+ Version: 0.1.87
4
4
  Summary: CopilotKit python SDK
5
- Home-page: https://copilotkit.ai
6
5
  License: MIT
7
6
  Keywords: copilot,copilotkit,langgraph,langchain,ai,langsmith,langserve
8
7
  Author: Markus Ecker
@@ -22,6 +21,7 @@ Requires-Dist: langchain (>=0.3.0)
22
21
  Requires-Dist: langgraph (>=0.3.25,<2)
23
22
  Requires-Dist: partialjson (>=0.0.8,<0.0.9)
24
23
  Requires-Dist: toml (>=0.10.2,<0.11.0)
24
+ Project-URL: Homepage, https://copilotkit.ai
25
25
  Project-URL: Repository, https://github.com/CopilotKit/CopilotKit/tree/main/sdk-python
26
26
  Description-Content-Type: text/markdown
27
27
 
@@ -18,6 +18,7 @@ Usage:
18
18
  from __future__ import annotations
19
19
 
20
20
  import json
21
+ from pathlib import Path
21
22
  from typing import Any
22
23
 
23
24
 
@@ -132,45 +133,38 @@ The List's own path ("/items") uses a leading slash (absolute), but all
132
133
  components INSIDE the template card use paths WITHOUT leading slash.
133
134
  Do NOT use "/items/0/name" or "/items/{@key}/name" — just "name".
134
135
 
135
- DATA MODEL:
136
- The "data" key in the tool args is a plain JSON object that initializes the surface
137
- data model. Components bound to paths (e.g. "value": { "path": "/form/name" })
138
- read from and write to this data model. Examples:
139
- For forms: "data": { "form": { "name": "Alice", "email": "" } }
140
- For lists: "data": { "items": [{"name": "Product A"}, {"name": "Product B"}] }
141
- For mixed: "data": { "form": { "query": "" }, "results": [...] }
142
-
143
- FORMS AND TWO-WAY DATA BINDING:
144
- To create editable forms, bind input components to data model paths using { "path": "..." }.
145
- The client automatically writes user input back to the data model at the bound path.
146
- CRITICAL: Using a literal value (e.g. "value": "") makes the field READ-ONLY.
147
- You MUST use { "path": "..." } to make inputs editable.
148
-
149
- All input components use "value" as the binding property:
150
- - TextField: "value": { "path": "/form/fieldName" }
151
- - CheckBox: "value": { "path": "/form/isChecked" }
152
- - Slider: "value": { "path": "/form/sliderVal" }
153
- - DateTimeInput: "value": { "path": "/form/date" }
154
- - ChoicePicker: "value": { "path": "/form/choices" }
155
-
156
- To retrieve form values when a button is clicked, include "context" with path references
157
- in the button's action. Paths are resolved to their current values at click time:
158
- "action": { "event": { "name": "submit", "context": { "userName": { "path": "/form/name" } } } }
159
-
160
- To pre-fill form values, pass initial data via the "data" tool argument:
161
- "data": { "form": { "name": "Markus" } }
162
-
163
- FORM EXAMPLE (editable text field with pre-filled value + submit button):
164
- "components": [
165
- { "id": "root", "component": "Card", "child": "form-col" },
166
- { "id": "form-col", "component": "Column", "children": ["name-field", "submit-row"] },
167
- { "id": "name-field", "component": "TextField", "label": "Name", "value": { "path": "/form/name" } },
168
- { "id": "submit-row", "component": "Row", "justify": "end", "children": ["submit-btn"] },
169
- { "id": "submit-btn", "component": "Button", "child": "btn-text", "variant": "primary",
170
- "action": { "event": { "name": "submit", "context": { "userName": { "path": "/form/name" } } } } },
171
- { "id": "btn-text", "component": "Text", "text": "Submit" }
172
- ],
173
- "data": { "form": { "name": "Markus" } }"""
136
+ COMPONENT VALUES — DEFAULT RULE:
137
+ Use inline literal values for ALL component properties. Pass strings, numbers,
138
+ arrays, and objects directly on the component. Do NOT use { "path": "..." }
139
+ objects unless the property's schema explicitly allows it (see exception below).
140
+ CRITICAL: USING { "path": "..." } ON A PROPERTY THAT DOES NOT DECLARE PATH
141
+ SUPPORT IN ITS SCHEMA WILL CAUSE A RUNTIME CRASH AND BREAK THE ENTIRE UI.
142
+ ALWAYS CHECK THE COMPONENT SCHEMA FIRST IF THE PROPERTY ONLY ACCEPTS A
143
+ PLAIN TYPE, YOU MUST USE A LITERAL VALUE.
144
+ VERY IMPORTANT: THE APPLICATION WILL BREAK IF YOU DO NOT FOLLOW THIS RULE!
145
+
146
+ For example, a chart's "data" must always be an inline array:
147
+ "data": [{"label": "Jan", "value": 100}, {"label": "Feb", "value": 200}]
148
+ A metric's "value" must always be an inline string:
149
+ "value": "$1,200"
150
+
151
+ PATH BINDING EXCEPTION — SCHEMA-DRIVEN:
152
+ A few properties accept { "path": "/some/path" } as an alternative to a literal
153
+ value. You can identify these in the Available Components schema: the property
154
+ will list BOTH a literal type AND an object-with-path option. If a property only
155
+ shows a single type (string, number, array, etc.), it does NOT support path
156
+ binding — use a literal value only.
157
+
158
+ Path binding is typically used for editable form inputs so the client can write
159
+ user input back to the data model. When building forms:
160
+ - Bind input "value" to a data model path: "value": { "path": "/form/name" }
161
+ - Pre-fill via the "data" tool argument: "data": { "form": { "name": "Alice" } }
162
+ - Capture values on submit via button action context:
163
+ "action": { "event": { "name": "submit", "context": { "name": { "path": "/form/name" } } } }
164
+
165
+ REPEATING CONTENT uses a structural children format (not the same as value binding):
166
+ children: { componentId: "card-id", path: "/items" }
167
+ Components inside templates use RELATIVE paths (no leading slash): { "path": "name" }."""
174
168
 
175
169
  DEFAULT_DESIGN_GUIDELINES = """\
176
170
  Create polished, visually appealing interfaces:
@@ -241,6 +235,3 @@ Use ONLY these components with the specified props.
241
235
 
242
236
  {component_schema}
243
237
  """
244
-
245
-
246
- return None
@@ -142,9 +142,19 @@ def langchain_messages_to_copilotkit(
142
142
  if hasattr(message, "content"):
143
143
  content = message.content
144
144
 
145
- # Check if content is a list and use the first element
145
+ # Content can be a list of content blocks (e.g. Anthropic models).
146
+ # Extract and concatenate all text parts instead of only taking
147
+ # the first element.
146
148
  if isinstance(content, list):
147
- content = content[0] if content else ""
149
+ text_parts = []
150
+ for part in content:
151
+ if isinstance(part, str):
152
+ text_parts.append(part)
153
+ elif isinstance(part, dict) and part.get("type") == "text":
154
+ text_parts.append(part.get("text", ""))
155
+ elif isinstance(part, dict) and "text" in part:
156
+ text_parts.append(part.get("text", ""))
157
+ content = "".join(text_parts)
148
158
 
149
159
  # Anthropic models return a dict with a "text" key
150
160
  if isinstance(content, dict):
@@ -2,6 +2,7 @@
2
2
 
3
3
  import uuid
4
4
  import json
5
+ import math
5
6
  from typing import Optional, List, Callable, Any, cast, Union, TypedDict, Literal
6
7
 
7
8
  from langgraph.graph.state import CompiledStateGraph
@@ -18,6 +19,22 @@ except ImportError:
18
19
  from langchain_core.messages import BaseMessage, SystemMessage
19
20
 
20
21
  from langchain_core.runnables import RunnableConfig, ensure_config
22
+
23
+ def _serialize_state(state):
24
+ """Recursively convert Pydantic BaseModel instances to dicts for serialization."""
25
+ try:
26
+ from pydantic import BaseModel as PydanticBaseModel
27
+ except ImportError:
28
+ return state
29
+
30
+ if isinstance(state, PydanticBaseModel):
31
+ return state.model_dump()
32
+ elif isinstance(state, dict):
33
+ return {k: _serialize_state(v) for k, v in state.items()}
34
+ elif isinstance(state, (list, tuple)):
35
+ return type(state)(_serialize_state(item) for item in state)
36
+ return state
37
+
21
38
  from langchain_core.messages import HumanMessage
22
39
 
23
40
  from partialjson.json_parser import JSONParser
@@ -31,6 +48,20 @@ from .logging import get_logger
31
48
 
32
49
  logger = get_logger(__name__)
33
50
 
51
+
52
+ def _sanitize_for_json(obj):
53
+ """Replace NaN and Infinity float values with None for valid JSON serialization."""
54
+ if isinstance(obj, float):
55
+ if math.isnan(obj) or math.isinf(obj):
56
+ return None
57
+ return obj
58
+ if isinstance(obj, dict):
59
+ return {k: _sanitize_for_json(v) for k, v in obj.items()}
60
+ if isinstance(obj, (list, tuple)):
61
+ return [_sanitize_for_json(item) for item in obj]
62
+ return obj
63
+
64
+
34
65
  class CopilotKitConfig(TypedDict):
35
66
  """
36
67
  CopilotKit config for LangGraphAgent
@@ -236,7 +267,12 @@ class LangGraphAgent(Agent):
236
267
  actions=actions,
237
268
  agent_name=self.name
238
269
  )
239
- current_graph_state.update(state)
270
+ # Only update graph state with keys that merge_state explicitly produced,
271
+ # not keys that were simply passed through from state_input unchanged.
272
+ # This preserves graph-owned state keys that the frontend may have sent stale values for.
273
+ for key, value in state.items():
274
+ if key not in state_input or value is not state_input.get(key):
275
+ current_graph_state[key] = value
240
276
  lg_interrupt_meta_event = next((ev for ev in (meta_events or []) if ev.get("name") == "LangGraphInterruptEvent"), None)
241
277
  has_active_interrupts = active_interrupts is not None and len(active_interrupts) > 0
242
278
 
@@ -435,7 +471,9 @@ class LangGraphAgent(Agent):
435
471
  manually_emitted_state = None
436
472
 
437
473
  if manually_emit_intermediate_state:
438
- manually_emitted_state = cast(Any, event["data"])
474
+ manually_emitted_state = _merge_emit_state(current_graph_state, cast(Any, event["data"]))
475
+ if isinstance(manually_emitted_state, dict):
476
+ current_graph_state.update(manually_emitted_state)
439
477
  yield self._emit_state_sync_event(
440
478
  thread_id=thread_id,
441
479
  run_id=run_id,
@@ -489,7 +527,7 @@ class LangGraphAgent(Agent):
489
527
  active=not exiting_node
490
528
  ) + "\n"
491
529
 
492
- yield langchain_dumps(event) + "\n"
530
+ yield langchain_dumps(_sanitize_for_json(event)) + "\n"
493
531
  except Exception as error:
494
532
  # Emit error information through streaming protocol before terminating
495
533
  # This preserves the semantic error details that would otherwise be lost
@@ -591,6 +629,9 @@ class LangGraphAgent(Agent):
591
629
  # Filter by schema keys if available
592
630
  state = self.filter_state_on_schema_keys(state, 'output')
593
631
 
632
+ # Convert Pydantic BaseModel instances to dicts for serialization
633
+ state = _serialize_state(state)
634
+
594
635
  return langchain_dumps({
595
636
  "event": "on_copilotkit_state_sync",
596
637
  "thread_id": thread_id,
@@ -598,7 +639,7 @@ class LangGraphAgent(Agent):
598
639
  "agent_name": self.name,
599
640
  "node_name": node_name,
600
641
  "active": active,
601
- "state": state,
642
+ "state": _sanitize_for_json(state),
602
643
  "running": running,
603
644
  "role": "assistant"
604
645
  })
@@ -636,10 +677,13 @@ class LangGraphAgent(Agent):
636
677
  state_copy = state.copy()
637
678
  state_copy.pop("messages", None)
638
679
 
680
+ # Convert Pydantic BaseModel instances to dicts for serialization
681
+ state_copy = _serialize_state(state_copy)
682
+
639
683
  return {
640
684
  "threadId": thread_id,
641
685
  "threadExists": True,
642
- "state": state_copy,
686
+ "state": _sanitize_for_json(state_copy),
643
687
  "messages": messages
644
688
  }
645
689
 
@@ -687,7 +731,8 @@ class LangGraphAgent(Agent):
687
731
  if hasattr(self, schema_keys_name) and getattr(self, schema_keys_name):
688
732
  return filter_by_schema_keys(state, getattr(self, schema_keys_name))
689
733
  except Exception:
690
- return state
734
+ pass
735
+ return state
691
736
 
692
737
  def get_interrupt_event(self, value):
693
738
  if not isinstance(value, str) and "__copilotkit_interrupt_value__" in value:
@@ -724,6 +769,13 @@ class LangGraphAgent(Agent):
724
769
 
725
770
  raise ValueError("Message ID not found in history")
726
771
 
772
+
773
+ def _merge_emit_state(current_state: dict, emitted_state: Any) -> dict:
774
+ """Merge emitted state on top of current graph state instead of replacing it."""
775
+ if isinstance(emitted_state, dict):
776
+ return {**current_state, **emitted_state}
777
+ return cast(Any, emitted_state)
778
+
727
779
  class _StreamingStateExtractor:
728
780
  def __init__(self, emit_intermediate_state: List[dict]):
729
781
  self.emit_intermediate_state = emit_intermediate_state
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "copilotkit"
3
- version = "0.1.86"
3
+ version = "0.1.87"
4
4
  description = "CopilotKit python SDK"
5
5
  authors = ["Markus Ecker <markus.ecker@gmail.com>"]
6
6
  license = "MIT"
File without changes