copilotkit 0.1.92__tar.gz → 0.1.94__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.92 → copilotkit-0.1.94}/PKG-INFO +3 -3
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/copilotkit_lg_middleware.py +262 -8
- {copilotkit-0.1.92 → copilotkit-0.1.94}/pyproject.toml +2 -2
- {copilotkit-0.1.92 → copilotkit-0.1.94}/README.md +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/__init__.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/a2ui.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/action.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/agent.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/crewai/__init__.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/crewai/copilotkit_integration.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/crewai/crewai_agent.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/crewai/crewai_sdk.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/exc.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/header_propagation.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/html.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/integrations/__init__.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/integrations/fastapi.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/langchain.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/langgraph.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/langgraph_agui_agent.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/logging.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/parameter.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/protocol.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/py.typed +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/runloop.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/sdk.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/types.py +0 -0
- {copilotkit-0.1.92 → copilotkit-0.1.94}/copilotkit/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: copilotkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.94
|
|
4
4
|
Summary: CopilotKit python SDK
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: copilot,copilotkit,langgraph,langchain,ai,langsmith,langserve
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Provides-Extra: crewai
|
|
18
|
-
Requires-Dist: ag-ui-langgraph[fastapi] (>=0.0.
|
|
18
|
+
Requires-Dist: ag-ui-langgraph[fastapi] (>=0.0.38)
|
|
19
19
|
Requires-Dist: ag-ui-protocol (>=0.1.15)
|
|
20
20
|
Requires-Dist: crewai (>=0.118.0) ; (python_version >= "3.10" and python_version < "3.14") and (extra == "crewai")
|
|
21
21
|
Requires-Dist: fastapi (>=0.115.0,<1.0.0)
|
|
@@ -30,10 +30,51 @@ from langgraph.runtime import Runtime
|
|
|
30
30
|
from .header_propagation import install_httpx_hook, set_forwarded_headers
|
|
31
31
|
from .langgraph import CopilotKitProperties
|
|
32
32
|
|
|
33
|
+
# Optional dependency: the A2UI subagent-tool factory ships in ag-ui-langgraph.
|
|
34
|
+
# Guarded so an older/skewed version without the factory degrades to
|
|
35
|
+
# "no auto-A2UI" instead of breaking the whole middleware import.
|
|
36
|
+
try: # pragma: no cover - exercised indirectly via the a2ui injection path
|
|
37
|
+
from ag_ui_langgraph import get_a2ui_tools
|
|
38
|
+
except Exception: # noqa: BLE001 - any import failure means the feature is off
|
|
39
|
+
get_a2ui_tools = None
|
|
40
|
+
|
|
33
41
|
# Track which httpx clients already have the header-propagation hook installed
|
|
34
42
|
# (by object id) so we never double-install on repeated model calls.
|
|
35
43
|
_hooked_clients: set[int] = set()
|
|
36
44
|
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Auto-A2UI: bridge the inferred model from the model-call hook to the
|
|
47
|
+
# tool-call hook
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# The generate_a2ui tool drives a structured-output subagent and so needs a
|
|
50
|
+
# chat model. We "infer" that model from ``request.model`` in
|
|
51
|
+
# ``wrap_model_call`` (the only hook that exposes the bound model) and reuse it.
|
|
52
|
+
# But the tool actually *executes* later in ``wrap_tool_call``, whose request
|
|
53
|
+
# does NOT carry the model. ContextVars do not reliably survive LangGraph node
|
|
54
|
+
# boundaries, so we bridge the built tool across nodes via a module-level map
|
|
55
|
+
# keyed by the run's thread id.
|
|
56
|
+
_a2ui_tools_by_thread: dict[str, Any] = {}
|
|
57
|
+
|
|
58
|
+
# Fallback key for runs without a thread id (e.g. an in-memory invoke with no
|
|
59
|
+
# checkpointer). Collisions across concurrent context-less runs are an
|
|
60
|
+
# acceptable edge — the deployed path always carries a thread id.
|
|
61
|
+
_DEFAULT_THREAD_KEY = "__copilotkit_a2ui_default__"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _current_thread_id() -> "str | None":
|
|
65
|
+
"""Best-effort read of the active run's thread id from the LangGraph config.
|
|
66
|
+
|
|
67
|
+
Returns ``None`` outside a runnable context (e.g. unit tests); callers then
|
|
68
|
+
fall back to ``_DEFAULT_THREAD_KEY``.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
from langgraph.config import get_config
|
|
72
|
+
|
|
73
|
+
cfg = get_config() or {}
|
|
74
|
+
return (cfg.get("configurable") or {}).get("thread_id")
|
|
75
|
+
except Exception: # noqa: BLE001 - no active context / older langgraph
|
|
76
|
+
return None
|
|
77
|
+
|
|
37
78
|
|
|
38
79
|
def _extract_forwarded_headers_from_config() -> None:
|
|
39
80
|
"""Extract raw ``x-*`` headers from the current LangGraph RunnableConfig and
|
|
@@ -154,6 +195,10 @@ _RESERVED_STATE_KEYS = frozenset(
|
|
|
154
195
|
{
|
|
155
196
|
"messages",
|
|
156
197
|
"copilotkit",
|
|
198
|
+
# Transport-layer plumbing: forwarded request headers conveyed via a
|
|
199
|
+
# separate ContextVar to the httpx hook. MUST never be rendered into
|
|
200
|
+
# the LLM prompt — neither via App Context nor via expose_state.
|
|
201
|
+
"copilotkit_forwarded_headers",
|
|
157
202
|
"ag-ui",
|
|
158
203
|
"tools",
|
|
159
204
|
"structured_response",
|
|
@@ -218,7 +263,17 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
218
263
|
if self._expose_state is False:
|
|
219
264
|
return None
|
|
220
265
|
if isinstance(self._expose_state, frozenset):
|
|
221
|
-
|
|
266
|
+
# Allowlist branch: honor user intent for other reserved keys
|
|
267
|
+
# (e.g. ``thread_id``) so the override test in this suite still
|
|
268
|
+
# passes, but hard-exclude ``copilotkit_forwarded_headers`` —
|
|
269
|
+
# rendering it would leak the raw forwarded request headers into
|
|
270
|
+
# the LLM prompt, which is what the reserved-keys comment above
|
|
271
|
+
# promises will never happen "via App Context nor via expose_state".
|
|
272
|
+
keys: list[str] = [
|
|
273
|
+
k
|
|
274
|
+
for k in self._expose_state
|
|
275
|
+
if k in state and k != "copilotkit_forwarded_headers"
|
|
276
|
+
]
|
|
222
277
|
else:
|
|
223
278
|
keys = [
|
|
224
279
|
k
|
|
@@ -259,7 +314,131 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
259
314
|
system_message=SystemMessage(content=f"{base}\n\n{note}")
|
|
260
315
|
)
|
|
261
316
|
|
|
262
|
-
#
|
|
317
|
+
# ------------------------------------------------------------------
|
|
318
|
+
# Auto-A2UI tool injection
|
|
319
|
+
# ------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _resolve_a2ui_catalog(state: dict) -> "tuple[str | None, str | None] | None":
|
|
323
|
+
"""Find the frontend-registered A2UI catalog wherever it was passed.
|
|
324
|
+
|
|
325
|
+
Returns ``(component_schema, catalog_id)`` when a catalog is present,
|
|
326
|
+
else ``None`` (so the tool is never advertised when the client can't
|
|
327
|
+
render A2UI). Two delivery paths are supported, because the catalog
|
|
328
|
+
lands in different places depending on how the agent is served:
|
|
329
|
+
|
|
330
|
+
- **AG-UI native endpoint** → ``state["ag-ui"]["a2ui_schema"]``, a JSON
|
|
331
|
+
string ``{"catalogId": ..., "components": [...]}``.
|
|
332
|
+
- **CopilotKit runtime proxy** → a ``state["copilotkit"]["context"]``
|
|
333
|
+
entry describing the A2UI catalog (catalog id + component schemas as
|
|
334
|
+
text).
|
|
335
|
+
|
|
336
|
+
``component_schema`` is the text/JSON the subagent should compose from;
|
|
337
|
+
``catalog_id`` binds generated surfaces to the frontend's catalog (so
|
|
338
|
+
BYOC custom catalogs render their own components, not the basic one).
|
|
339
|
+
"""
|
|
340
|
+
# AG-UI native path.
|
|
341
|
+
ag_ui = state.get("ag-ui") or {}
|
|
342
|
+
a2ui_schema = ag_ui.get("a2ui_schema")
|
|
343
|
+
if a2ui_schema:
|
|
344
|
+
catalog_id = None
|
|
345
|
+
try:
|
|
346
|
+
parsed = (
|
|
347
|
+
json.loads(a2ui_schema)
|
|
348
|
+
if isinstance(a2ui_schema, str)
|
|
349
|
+
else a2ui_schema
|
|
350
|
+
)
|
|
351
|
+
if isinstance(parsed, dict):
|
|
352
|
+
catalog_id = parsed.get("catalogId")
|
|
353
|
+
except (TypeError, ValueError):
|
|
354
|
+
pass
|
|
355
|
+
# Native path: the toolkit reads ``a2ui_schema`` from state itself,
|
|
356
|
+
# so no composition_guide is needed — just surface the catalog id.
|
|
357
|
+
return None, catalog_id
|
|
358
|
+
|
|
359
|
+
# CopilotKit runtime-proxy path: the catalog arrives as a context entry.
|
|
360
|
+
context = (state.get("copilotkit") or {}).get("context") or []
|
|
361
|
+
for entry in context:
|
|
362
|
+
if not isinstance(entry, dict):
|
|
363
|
+
continue
|
|
364
|
+
description = entry.get("description") or ""
|
|
365
|
+
value = entry.get("value") or ""
|
|
366
|
+
if "A2UI catalog" not in description or not value:
|
|
367
|
+
continue
|
|
368
|
+
# The value lists catalogs as "- <catalogId>" lines; the first is
|
|
369
|
+
# the custom catalog the client registered.
|
|
370
|
+
match = re.search(r"(?m)^\s*-\s+(\S+)", value)
|
|
371
|
+
catalog_id = match.group(1) if match else None
|
|
372
|
+
return value, catalog_id
|
|
373
|
+
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _a2ui_inject_decision(state: dict) -> "bool | str | None":
|
|
378
|
+
"""Return the A2UI ``injectA2UITool`` decision, or ``None``.
|
|
379
|
+
|
|
380
|
+
The ``@ag-ui/a2ui-middleware`` forwards its ``injectA2UITool`` setting on
|
|
381
|
+
``forwardedProps``, which ``ag-ui-langgraph`` surfaces into agent state at
|
|
382
|
+
``state["ag-ui"]["inject_a2ui_tool"]`` — present only when the host turned
|
|
383
|
+
the runtime A2UI tool on (truthy or a custom tool-name string). ``None``
|
|
384
|
+
means no signal at all (off, or no A2UI middleware in the pipeline), in
|
|
385
|
+
which case we do not auto-inject.
|
|
386
|
+
"""
|
|
387
|
+
return (state.get("ag-ui") or {}).get("inject_a2ui_tool")
|
|
388
|
+
|
|
389
|
+
def _maybe_build_a2ui_tool(self, request: ModelRequest) -> Any | None:
|
|
390
|
+
"""Build a ``generate_a2ui`` tool bound to the agent's own model when
|
|
391
|
+
A2UI tool injection is turned on for this run.
|
|
392
|
+
|
|
393
|
+
Gating, in order:
|
|
394
|
+
|
|
395
|
+
1. **Opt-in.** Only inject when the A2UI ``injectA2UITool`` flag is
|
|
396
|
+
truthy (forwarded by ``@ag-ui/a2ui-middleware`` and surfaced at
|
|
397
|
+
``state["ag-ui"]["inject_a2ui_tool"]``). No flag → no injection. This
|
|
398
|
+
is the whole contract: "no injectA2UITool, no A2UI tool injection."
|
|
399
|
+
2. **No double-inject.** If the agent already exposes a tool with the
|
|
400
|
+
same name (e.g. a backend-defined ``generate_a2ui``), don't inject —
|
|
401
|
+
the host owns it, and a duplicate would show the model two tools with
|
|
402
|
+
one name.
|
|
403
|
+
|
|
404
|
+
The model is inferred from ``request.model`` (the bound agent model); the
|
|
405
|
+
component schema and catalog id come from the registered catalog (when
|
|
406
|
+
present) so the subagent composes the right components and surfaces bind
|
|
407
|
+
to the frontend's catalog — otherwise the toolkit's basic catalog is
|
|
408
|
+
used. The built tool is stashed for the tool-call hook to execute.
|
|
409
|
+
Returns the tool or ``None`` when A2UI is not applicable.
|
|
410
|
+
"""
|
|
411
|
+
if get_a2ui_tools is None:
|
|
412
|
+
return None
|
|
413
|
+
state = request.state or {}
|
|
414
|
+
|
|
415
|
+
# (1) Opt-in: only inject when the host turned the A2UI tool on.
|
|
416
|
+
if not self._a2ui_inject_decision(state):
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
# Bind to the frontend's catalog when one was registered (optional).
|
|
420
|
+
resolved = self._resolve_a2ui_catalog(state)
|
|
421
|
+
component_schema, catalog_id = resolved if resolved else (None, None)
|
|
422
|
+
|
|
423
|
+
kwargs: dict[str, Any] = {}
|
|
424
|
+
if catalog_id:
|
|
425
|
+
kwargs["default_catalog_id"] = catalog_id
|
|
426
|
+
# Feed the registered component schema to the subagent so it composes
|
|
427
|
+
# only catalog components (the toolkit appends this to its prompt).
|
|
428
|
+
if component_schema:
|
|
429
|
+
kwargs["composition_guide"] = component_schema
|
|
430
|
+
|
|
431
|
+
tool = get_a2ui_tools(request.model, **kwargs)
|
|
432
|
+
|
|
433
|
+
# (2) Don't double-inject if the agent already defines this tool.
|
|
434
|
+
existing_names = {getattr(t, "name", None) for t in (request.tools or [])}
|
|
435
|
+
if tool.name in existing_names:
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
_a2ui_tools_by_thread[_current_thread_id() or _DEFAULT_THREAD_KEY] = tool
|
|
439
|
+
return tool
|
|
440
|
+
|
|
441
|
+
# Inject frontend + A2UI tools and surface user state before model call
|
|
263
442
|
def wrap_model_call(
|
|
264
443
|
self,
|
|
265
444
|
request: ModelRequest,
|
|
@@ -268,13 +447,25 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
268
447
|
_extract_forwarded_headers_from_config()
|
|
269
448
|
_ensure_httpx_hook(request.model)
|
|
270
449
|
request = self._apply_state_note(request)
|
|
450
|
+
|
|
451
|
+
a2ui_tool = self._maybe_build_a2ui_tool(request)
|
|
271
452
|
frontend_tools = request.state.get("copilotkit", {}).get("actions", [])
|
|
453
|
+
if a2ui_tool is not None:
|
|
454
|
+
# Our generate_a2ui replaces the runtime's render tool — don't
|
|
455
|
+
# advertise both. Drop the render tool the A2UI middleware injected.
|
|
456
|
+
decision = self._a2ui_inject_decision(request.state or {})
|
|
457
|
+
drop = decision if isinstance(decision, str) else "render_a2ui"
|
|
458
|
+
frontend_tools = [
|
|
459
|
+
t
|
|
460
|
+
for t in frontend_tools
|
|
461
|
+
if ((t.get("function") or {}).get("name") or t.get("name")) != drop
|
|
462
|
+
]
|
|
272
463
|
|
|
273
|
-
if not frontend_tools:
|
|
464
|
+
if not frontend_tools and a2ui_tool is None:
|
|
274
465
|
return handler(request)
|
|
275
466
|
|
|
276
|
-
|
|
277
|
-
merged_tools = [*request.tools, *frontend_tools]
|
|
467
|
+
extra_tools = [a2ui_tool] if a2ui_tool is not None else []
|
|
468
|
+
merged_tools = [*request.tools, *extra_tools, *frontend_tools]
|
|
278
469
|
|
|
279
470
|
return handler(request.override(tools=merged_tools))
|
|
280
471
|
|
|
@@ -460,16 +651,62 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
460
651
|
self._fix_messages_for_bedrock(request.messages)
|
|
461
652
|
request = self._apply_state_note(request)
|
|
462
653
|
|
|
654
|
+
a2ui_tool = self._maybe_build_a2ui_tool(request)
|
|
463
655
|
frontend_tools = request.state.get("copilotkit", {}).get("actions", [])
|
|
656
|
+
if a2ui_tool is not None:
|
|
657
|
+
# Our generate_a2ui replaces the runtime's render tool — don't
|
|
658
|
+
# advertise both. Drop the render tool the A2UI middleware injected.
|
|
659
|
+
decision = self._a2ui_inject_decision(request.state or {})
|
|
660
|
+
drop = decision if isinstance(decision, str) else "render_a2ui"
|
|
661
|
+
frontend_tools = [
|
|
662
|
+
t
|
|
663
|
+
for t in frontend_tools
|
|
664
|
+
if ((t.get("function") or {}).get("name") or t.get("name")) != drop
|
|
665
|
+
]
|
|
464
666
|
|
|
465
|
-
if not frontend_tools:
|
|
667
|
+
if not frontend_tools and a2ui_tool is None:
|
|
466
668
|
return await handler(request)
|
|
467
669
|
|
|
468
|
-
|
|
469
|
-
merged_tools = [*request.tools, *frontend_tools]
|
|
670
|
+
extra_tools = [a2ui_tool] if a2ui_tool is not None else []
|
|
671
|
+
merged_tools = [*request.tools, *extra_tools, *frontend_tools]
|
|
470
672
|
|
|
471
673
|
return await handler(request.override(tools=merged_tools))
|
|
472
674
|
|
|
675
|
+
# ------------------------------------------------------------------
|
|
676
|
+
# Auto-A2UI tool execution
|
|
677
|
+
# ------------------------------------------------------------------
|
|
678
|
+
# The generate_a2ui tool is advertised dynamically in wrap_model_call and is
|
|
679
|
+
# NOT in create_agent's static tool registry, so the tool node cannot
|
|
680
|
+
# execute it on its own. These hooks supply the implementation (built with
|
|
681
|
+
# the inferred model) for that one tool; their presence also disables
|
|
682
|
+
# create_agent's "unknown tool" guard for dynamically-advertised tools.
|
|
683
|
+
|
|
684
|
+
def _resolve_a2ui_request(self, request: Any) -> Any:
|
|
685
|
+
"""Return a request overridden with the stashed A2UI tool when this
|
|
686
|
+
tool call targets it, else the original request unchanged."""
|
|
687
|
+
tool = _a2ui_tools_by_thread.get(_current_thread_id() or _DEFAULT_THREAD_KEY)
|
|
688
|
+
if (
|
|
689
|
+
tool is not None
|
|
690
|
+
and getattr(request, "tool", None) is None
|
|
691
|
+
and request.tool_call.get("name") == tool.name
|
|
692
|
+
):
|
|
693
|
+
return request.override(tool=tool)
|
|
694
|
+
return request
|
|
695
|
+
|
|
696
|
+
def wrap_tool_call(
|
|
697
|
+
self,
|
|
698
|
+
request: Any,
|
|
699
|
+
handler: Callable[[Any], Any],
|
|
700
|
+
) -> Any:
|
|
701
|
+
return handler(self._resolve_a2ui_request(request))
|
|
702
|
+
|
|
703
|
+
async def awrap_tool_call(
|
|
704
|
+
self,
|
|
705
|
+
request: Any,
|
|
706
|
+
handler: Callable[[Any], Awaitable[Any]],
|
|
707
|
+
) -> Any:
|
|
708
|
+
return await handler(self._resolve_a2ui_request(request))
|
|
709
|
+
|
|
473
710
|
# Inject app context before agent runs
|
|
474
711
|
def before_agent(
|
|
475
712
|
self,
|
|
@@ -487,6 +724,19 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
487
724
|
runtime, "context", None
|
|
488
725
|
)
|
|
489
726
|
|
|
727
|
+
# Strip the reserved transport-layer key ``copilotkit_forwarded_headers``
|
|
728
|
+
# so it is never rendered into the LLM prompt. langgraph-api auto-copies
|
|
729
|
+
# ``config.configurable`` into ``runtime.context``, which means the
|
|
730
|
+
# forwarded-headers wrapper dict shows up here even though it is only
|
|
731
|
+
# meant for the httpx hook (which reads it from a separate ContextVar
|
|
732
|
+
# via ``_extract_forwarded_headers_from_config``).
|
|
733
|
+
if isinstance(app_context, dict):
|
|
734
|
+
app_context = {
|
|
735
|
+
k: v
|
|
736
|
+
for k, v in app_context.items()
|
|
737
|
+
if k != "copilotkit_forwarded_headers"
|
|
738
|
+
}
|
|
739
|
+
|
|
490
740
|
# Check if app_context is missing or empty
|
|
491
741
|
if not app_context:
|
|
492
742
|
return None
|
|
@@ -651,6 +901,10 @@ class CopilotKitMiddleware(AgentMiddleware[StateSchema, Any]):
|
|
|
651
901
|
state: StateSchema,
|
|
652
902
|
runtime: Runtime[Any],
|
|
653
903
|
) -> dict[str, Any] | None:
|
|
904
|
+
# Drop the bridged A2UI tool for this run — all tool calls for the turn
|
|
905
|
+
# have executed by now; the next model call re-stashes if needed.
|
|
906
|
+
_a2ui_tools_by_thread.pop(_current_thread_id() or _DEFAULT_THREAD_KEY, None)
|
|
907
|
+
|
|
654
908
|
copilotkit_state = state.get("copilotkit", {})
|
|
655
909
|
intercepted_tool_calls = copilotkit_state.get("intercepted_tool_calls")
|
|
656
910
|
original_message_id = copilotkit_state.get("original_ai_message_id")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "copilotkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.94"
|
|
4
4
|
description = "CopilotKit python SDK"
|
|
5
5
|
authors = ["Markus Ecker <markus.ecker@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -31,7 +31,7 @@ python = ">=3.10,<3.15"
|
|
|
31
31
|
langgraph = { version = ">=0.3.25,<2" }
|
|
32
32
|
langchain = { version = ">=0.3.0" }
|
|
33
33
|
crewai = { version = ">=0.118.0", optional = true, python = ">=3.10,<3.14" }
|
|
34
|
-
ag-ui-langgraph = { version = ">=0.0.
|
|
34
|
+
ag-ui-langgraph = { version = ">=0.0.38", extras = ["fastapi"] }
|
|
35
35
|
ag-ui-protocol = ">=0.1.15"
|
|
36
36
|
fastapi = ">=0.115.0,<1.0.0"
|
|
37
37
|
partialjson = "^0.0.8"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|