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.
- {copilotkit-0.1.86 → copilotkit-0.1.87}/PKG-INFO +3 -3
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/a2ui.py +33 -42
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph.py +12 -2
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph_agent.py +58 -6
- {copilotkit-0.1.86 → copilotkit-0.1.87}/pyproject.toml +1 -1
- {copilotkit-0.1.86 → copilotkit-0.1.87}/README.md +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/__init__.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/action.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/agent.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/copilotkit_lg_middleware.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/__init__.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/copilotkit_integration.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/crewai_agent.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/crewai/crewai_sdk.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/exc.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/html.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/integrations/__init__.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/integrations/fastapi.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langchain.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/langgraph_agui_agent.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/logging.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/parameter.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/protocol.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/runloop.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/sdk.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/types.py +0 -0
- {copilotkit-0.1.86 → copilotkit-0.1.87}/copilotkit/utils.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: copilotkit
|
|
3
|
-
Version: 0.1.
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|