Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/a2ui-renderer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/a2ui-renderer",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"description": "A2UI Renderer for CopilotKit - render A2UI surfaces in React applications",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion packages/agentcore-runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/agentcore-runner",
"version": "1.58.0",
"version": "1.59.0",
"description": "AWS Bedrock AgentCore-compatible agent runner for CopilotKit2",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/core",
"version": "1.58.0",
"version": "1.59.0",
"description": "Core web utilities for CopilotKit2",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/react-core",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/react-native",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-textarea/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/react-textarea",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/react-ui",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-client-gql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/runtime-client-gql",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/runtime",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/sdk-js",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/shared",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"keywords": [
"ai",
Expand Down
2 changes: 1 addition & 1 deletion packages/sqlite-runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/sqlite-runner",
"version": "1.58.0",
"version": "1.59.0",
"description": "SQLite-backed agent runner for CopilotKit2",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/voice/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/voice",
"version": "1.58.0",
"version": "1.59.0",
"description": "Voice services for CopilotKit (transcription, text-to-speech, etc.)",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/vue",
"version": "1.58.0",
"version": "1.59.0",
"private": false,
"description": "Vue 3 components and composables for CopilotKit",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion packages/web-inspector/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/web-inspector",
"version": "1.58.0",
"version": "1.59.0",
"description": "Lit-based web component for the CopilotKit web inspector",
"repository": {
"type": "git",
Expand Down
29 changes: 28 additions & 1 deletion sdk-python/copilotkit/copilotkit_lg_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ class StateSchema(AgentState):
{
"messages",
"copilotkit",
# Transport-layer plumbing: forwarded request headers conveyed via a
# separate ContextVar to the httpx hook. MUST never be rendered into
# the LLM prompt — neither via App Context nor via expose_state.
"copilotkit_forwarded_headers",
"ag-ui",
"tools",
"structured_response",
Expand Down Expand Up @@ -218,7 +222,17 @@ def _build_state_note(self, state: dict) -> str | None:
if self._expose_state is False:
return None
if isinstance(self._expose_state, frozenset):
keys: list[str] = [k for k in self._expose_state if k in state]
# Allowlist branch: honor user intent for other reserved keys
# (e.g. ``thread_id``) so the override test in this suite still
# passes, but hard-exclude ``copilotkit_forwarded_headers`` —
# rendering it would leak the raw forwarded request headers into
# the LLM prompt, which is what the reserved-keys comment above
# promises will never happen "via App Context nor via expose_state".
keys: list[str] = [
k
for k in self._expose_state
if k in state and k != "copilotkit_forwarded_headers"
]
else:
keys = [
k
Expand Down Expand Up @@ -487,6 +501,19 @@ def before_agent(
runtime, "context", None
)

# Strip the reserved transport-layer key ``copilotkit_forwarded_headers``
# so it is never rendered into the LLM prompt. langgraph-api auto-copies
# ``config.configurable`` into ``runtime.context``, which means the
# forwarded-headers wrapper dict shows up here even though it is only
# meant for the httpx hook (which reads it from a separate ContextVar
# via ``_extract_forwarded_headers_from_config``).
if isinstance(app_context, dict):
app_context = {
k: v
for k, v in app_context.items()
if k != "copilotkit_forwarded_headers"
}

# Check if app_context is missing or empty
if not app_context:
return None
Expand Down
2 changes: 1 addition & 1 deletion sdk-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "copilotkit"
version = "0.1.92"
version = "0.1.93"
description = "CopilotKit python SDK"
authors = ["Markus Ecker <markus.ecker@gmail.com>"]
license = "MIT"
Expand Down
142 changes: 142 additions & 0 deletions sdk-python/tests/test_copilotkit_lg_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,74 @@ def test_expose_state_emits_valid_json_payload():
assert parsed == {"liked": ["a", "b"], "count": 3, "nested": {"k": "v"}}


def test_expose_state_true_never_surfaces_forwarded_headers():
"""``copilotkit_forwarded_headers`` is a transport-layer plumbing key — it
must NEVER reach the LLM prompt via the ``expose_state`` path either, even
when ``expose_state=True`` would otherwise serialize every non-reserved
top-level state key.
"""
middleware = CopilotKitMiddleware(expose_state=True)
request = _make_request(
state={
"messages": [],
"liked": ["a"],
"copilotkit_forwarded_headers": {
"x-aimock-context": "showcase/d6",
"x-aimock-strict": "true",
},
}
)

seen, _ = _run_wrap(middleware, request)

body = seen.system_message.content if seen.system_message else ""
# The genuine user key is still surfaced.
assert '"liked"' in body
# The transport-layer wrapper and its header values are not.
assert "copilotkit_forwarded_headers" not in body, (
"copilotkit_forwarded_headers must never appear in expose_state output"
)
assert "x-aimock-context" not in body, (
"forwarded header values must never appear in expose_state output"
)
assert "x-aimock-strict" not in body
assert "showcase/d6" not in body


def test_expose_state_allowlist_never_surfaces_forwarded_headers():
"""``copilotkit_forwarded_headers`` must NEVER reach the LLM prompt via the
``expose_state`` path — including the explicit allowlist form. The sibling
``test_expose_state_allowlist_can_override_reserved_keys`` pins that users
CAN allowlist other reserved keys (e.g. ``thread_id``) on purpose; this key
is the one exception, because it is a transport-layer wrapper for forwarded
request headers and rendering it would leak the raw headers into the prompt.
"""
middleware = CopilotKitMiddleware(
expose_state=frozenset({"copilotkit_forwarded_headers"})
)
request = _make_request(
state={
"messages": [],
"copilotkit_forwarded_headers": {
"x-aimock-context": "showcase/d6",
"x-aimock-strict": "true",
},
}
)

seen, _ = _run_wrap(middleware, request)

body = seen.system_message.content if seen.system_message else ""
assert "copilotkit_forwarded_headers" not in body, (
"copilotkit_forwarded_headers must never appear in allowlist output"
)
assert "x-aimock-context" not in body, (
"forwarded header values must never appear in allowlist output"
)
assert "x-aimock-strict" not in body
assert "showcase/d6" not in body


# ---------------------------------------------------------------------------
# Async wrapper parity
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -410,6 +478,80 @@ def test_before_agent_uses_runtime_context_when_state_context_empty():
assert any("/dashboard" in s for s in sys_contents)


def test_before_agent_strips_copilotkit_forwarded_headers_from_runtime_context():
"""``copilotkit_forwarded_headers`` is a transport-layer plumbing key that
langgraph-api auto-copies from ``configurable`` into ``context``. It must
never be rendered into the LLM prompt as App Context — the forwarded-headers
httpx conveyance path reads it from a separate ContextVar.

When that key is the ONLY thing in ``runtime.context``, ``before_agent``
must treat it as empty App Context and not inject an "App Context:" system
message at all.
"""
middleware = CopilotKitMiddleware()
state = {"messages": [HumanMessage("hi")], "copilotkit": {}}
runtime = MagicMock(
name="runtime",
context={
"copilotkit_forwarded_headers": {
"x-aimock-context": "showcase/d6",
"x-aimock-strict": "true",
}
},
)

result = middleware.before_agent(state, runtime)

# Contract: when ``runtime.context`` contains ONLY
# ``copilotkit_forwarded_headers``, the strip leaves an empty App Context,
# so ``before_agent`` MUST short-circuit and return None (no App Context
# system message). A non-None result here means the strip regressed and
# the transport-layer wrapper is being injected into the prompt.
#
# NOTE: an earlier version of this test guarded the leak assertions with
# ``if result is not None``, which silently passed if the strip stopped
# short-circuiting — exactly the regression we need to catch. Assert
# explicitly instead.
assert result is None, (
"expected short-circuit (no App Context message) when only "
"copilotkit_forwarded_headers is present in runtime.context"
)


def test_before_agent_strips_forwarded_headers_but_keeps_real_app_context():
"""When ``runtime.context`` contains both a genuine app key AND the
transport-only ``copilotkit_forwarded_headers`` wrapper, the App Context
system message must still be injected with the real key, but the forwarded
headers must be filtered out.
"""
middleware = CopilotKitMiddleware()
state = {"messages": [HumanMessage("hi")], "copilotkit": {}}
runtime = MagicMock(
name="runtime",
context={
"user_tier": "pro",
"copilotkit_forwarded_headers": {
"x-aimock-context": "showcase/d6",
},
},
)

result = middleware.before_agent(state, runtime)

assert result is not None
sys_contents = _system_contents(result["messages"])
# The genuine app context is still surfaced.
assert any("App Context:" in s for s in sys_contents)
assert any("user_tier" in s and "pro" in s for s in sys_contents)
# The transport-layer wrapper is stripped from the rendered prompt.
assert not any("copilotkit_forwarded_headers" in s for s in sys_contents), (
"copilotkit_forwarded_headers must be filtered out of the App Context message"
)
assert not any("x-aimock-context" in s for s in sys_contents), (
"forwarded header values must never appear in a system prompt"
)


# ---------------------------------------------------------------------------
# after_model — frontend tool-call interception
# ---------------------------------------------------------------------------
Expand Down
16 changes: 4 additions & 12 deletions showcase/shell-docs/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ const nextConfig: NextConfig = {
},
{
source: "/learn/tutorials/multi-conversation-chat",
destination: "/tutorials/multi-conversation-chat",
destination: "/",
permanent: true,
},
{
Expand Down Expand Up @@ -507,11 +507,11 @@ const nextConfig: NextConfig = {
destination: "/coding-agents",
permanent: false,
},
// Orphaned broken stub.
// Old guide now belongs in the v2 hook reference.
{
source: "/copilot-suggestions",
destination: "/",
permanent: false,
destination: "/reference/v2/hooks/useSuggestions",
permanent: true,
},
// AI-slop placeholder pulled from nav until properly authored;
// file stays on disk for rewrite.
Expand All @@ -520,14 +520,6 @@ const nextConfig: NextConfig = {
destination: "/generative-ui",
permanent: false,
},
// ~1-year-old migration target, no longer a meaningful jump-off
// point.
{
source: "/migrate/1.10.X",
destination: "/migrate/v2",
permanent: false,
},

// ag-ui-middleware moved into the agentic-protocols group so it
// appears in the sidebar under AG-UI rather than as an orphan
// root page. 302 (not 301) since the new home is recent and we
Expand Down
1 change: 1 addition & 0 deletions showcase/shell-docs/src/components/docs-landing-next.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const backendDescriptions: Record<string, string> = {
"Python LangGraph agents with the broadest feature coverage.",
"langgraph-typescript": "TypeScript LangGraph agents over the AG-UI adapter.",
"langgraph-fastapi": "Python LangGraph agents exposed through FastAPI.",
deepagents: "LangChain Deep Agents connected to CopilotKit product UI.",
"google-adk": "Gemini-powered Google ADK agents connected through AG-UI.",
mastra: "TypeScript-native agents, tools, memory, and workflows.",
"crewai-crews": "CrewAI crews wired into CopilotKit product interfaces.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"mcp-servers",
"model-selection",
"advanced-configuration",
"telemetry",
"---Backend---",
"copilot-runtime", "custom-agent", "ag-ui",
"---Premium Features---",
Expand Down
Loading
Loading