Skip to content

Commit 21d2051

Browse files
marthakellyclaude
andcommitted
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>
1 parent a90dbce commit 21d2051

4 files changed

Lines changed: 201 additions & 26 deletions

File tree

examples/canvas/gemini/agent/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from fastapi import FastAPI
1111
import uvicorn
1212
from copilotkit.integrations.fastapi import add_fastapi_endpoint
13-
from copilotkit import CopilotKitSDK, LangGraphAgent
13+
from copilotkit import CopilotKitSDK, LangGraphAGUIAgent
1414
from posts_generator_agent import post_generation_graph
1515
from stack_agent import stack_analysis_graph
1616

@@ -19,12 +19,12 @@
1919

2020
sdk = CopilotKitSDK(
2121
agents=[
22-
LangGraphAgent(
22+
LangGraphAGUIAgent(
2323
name="post_generation_agent",
2424
description="An agent that can help with the generation of LinkedIn posts and X posts.",
2525
graph=post_generation_graph,
2626
),
27-
LangGraphAgent(
27+
LangGraphAGUIAgent(
2828
name="stack_analysis_agent",
2929
description="Analyze a GitHub repository URL to infer purpose and tech stack (frontend, backend, DB, infra).",
3030
graph=stack_analysis_graph,

examples/v1/_legacy/saas-dynamic-dashboards/agent/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import FastAPI
55
import uvicorn
66
from copilotkit.integrations.fastapi import add_fastapi_endpoint
7-
from copilotkit import CopilotKitSDK, LangGraphAgent
7+
from copilotkit import CopilotKitSDK, LangGraphAGUIAgent
88
import os
99
import uuid
1010
import json
@@ -406,7 +406,7 @@ async def chat_node(state: Dict[str, Any], config: RunnableConfig):
406406

407407
sdk = CopilotKitSDK(
408408
agents=[
409-
LangGraphAgent(
409+
LangGraphAGUIAgent(
410410
name="testing_agent",
411411
description="An example for a testing agent.",
412412
graph=testing_graph,

sdk-python/poetry.lock

Lines changed: 74 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk-python/tests/test_agui_agent.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,125 @@ def test_manually_emit_tool_call_with_empty_args(self, agent):
500500
assert EventType.TOOL_CALL_START in types
501501
assert EventType.TOOL_CALL_ARGS in types
502502
assert EventType.TOOL_CALL_END in types
503+
504+
505+
# ---------- AG-UI-style (unprefixed) event integration ----------
506+
507+
class TestAGUIStyleEventIntegration:
508+
"""Verify that AG-UI-native (unprefixed) events flow correctly through CopilotKit.
509+
510+
CopilotKit's _dispatch_event only intercepts copilotkit_-prefixed CUSTOM events;
511+
unprefixed AG-UI events (manually_emit_message, manually_emit_tool_call, exit) are
512+
delegated to the AG-UI base class — never suppressed or double-converted by CopilotKit.
513+
"""
514+
515+
def _make_agent(self):
516+
mock_graph = MagicMock()
517+
mock_graph.get_state = MagicMock()
518+
agent = LangGraphAGUIAgent(name="test", graph=mock_graph)
519+
agent.active_run = {
520+
"id": "run-1", "thread_id": "t1",
521+
"reasoning_process": None, "node_name": "agent",
522+
"has_function_streaming": False, "model_made_tool_call": False,
523+
"state_reliable": True, "streamed_messages": [],
524+
"manually_emitted_state": None,
525+
"schema_keys": {
526+
"input": ["messages", "tools"],
527+
"output": ["messages", "tools"],
528+
"config": [],
529+
"context": [],
530+
},
531+
}
532+
return agent
533+
534+
def test_agui_manually_emit_message_produces_text_events(self):
535+
"""on_custom_event/"manually_emit_message" through CopilotKit should produce
536+
TEXT_MESSAGE_START/CONTENT/END via the AG-UI base handler — not suppressed."""
537+
import asyncio
538+
agent = self._make_agent()
539+
lg_event = {
540+
"event": "on_custom_event",
541+
"name": "manually_emit_message",
542+
"data": {"message_id": "msg-1", "message": "Hello", "role": "assistant"},
543+
"metadata": {},
544+
}
545+
546+
async def _run():
547+
async for _ in agent._handle_single_event(lg_event, {}):
548+
pass
549+
550+
with track_parent_dispatches(agent) as dispatched:
551+
asyncio.run(_run())
552+
553+
types = [e.type for e in dispatched]
554+
assert EventType.TEXT_MESSAGE_START in types
555+
assert EventType.TEXT_MESSAGE_CONTENT in types
556+
assert EventType.TEXT_MESSAGE_END in types
557+
558+
def test_agui_manually_emit_tool_call_produces_tool_events(self):
559+
"""on_custom_event/"manually_emit_tool_call" through CopilotKit should produce
560+
TOOL_CALL_START/ARGS/END via the AG-UI base handler."""
561+
import asyncio
562+
agent = self._make_agent()
563+
lg_event = {
564+
"event": "on_custom_event",
565+
"name": "manually_emit_tool_call",
566+
"data": {"id": "tc-1", "name": "search", "args": {"q": "weather"}},
567+
"metadata": {},
568+
}
569+
570+
async def _run():
571+
async for _ in agent._handle_single_event(lg_event, {}):
572+
pass
573+
574+
with track_parent_dispatches(agent) as dispatched:
575+
asyncio.run(_run())
576+
577+
types = [e.type for e in dispatched]
578+
assert EventType.TOOL_CALL_START in types
579+
assert EventType.TOOL_CALL_ARGS in types
580+
assert EventType.TOOL_CALL_END in types
581+
582+
def test_agui_exit_produces_custom_event(self):
583+
"""on_custom_event/"exit" through CopilotKit should emit a CUSTOM event —
584+
the exit signal is not suppressed by the CopilotKit layer."""
585+
import asyncio
586+
agent = self._make_agent()
587+
lg_event = {
588+
"event": "on_custom_event",
589+
"name": "exit",
590+
"data": {},
591+
"metadata": {},
592+
}
593+
594+
async def _run():
595+
async for _ in agent._handle_single_event(lg_event, {}):
596+
pass
597+
598+
with track_parent_dispatches(agent) as dispatched:
599+
asyncio.run(_run())
600+
601+
types = [e.type for e in dispatched]
602+
assert EventType.CUSTOM in types
603+
604+
def test_agui_style_event_not_suppressed_by_dispatch(self):
605+
"""A CUSTOM event with an AG-UI-style unprefixed name passed to CopilotKit's
606+
_dispatch_event should be forwarded to the base class unchanged.
607+
608+
CopilotKit only converts copilotkit_-prefixed events (e.g. copilotkit_manually_emit_message
609+
→ TEXT_MESSAGE_*). An unprefixed "manually_emit_message" CustomEvent must not trigger
610+
that conversion path — no TEXT_MESSAGE_START should be injected, and the original
611+
event object must be forwarded as-is."""
612+
agent = self._make_agent()
613+
original = CustomEvent(
614+
type=EventType.CUSTOM,
615+
name="manually_emit_message",
616+
value={"message_id": "msg-2", "message": "test", "role": "assistant"},
617+
)
618+
with track_parent_dispatches(agent) as dispatched:
619+
agent._dispatch_event(original)
620+
621+
types = [e.type for e in dispatched]
622+
assert EventType.CUSTOM in types
623+
assert EventType.TEXT_MESSAGE_START not in types
624+
assert dispatched[0] is original

0 commit comments

Comments
 (0)