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/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"@ag-ui/client": "0.0.56",
"@ag-ui/core": "0.0.56",
"@ag-ui/encoder": "0.0.56",
"@ag-ui/langgraph": "0.0.39",
"@ag-ui/langgraph": "0.0.41",
"@ag-ui/mcp-apps-middleware": "0.0.3",
"@ag-ui/mcp-middleware": "0.0.1",
"@ai-sdk/anthropic": "^3.0.49",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"attw": "attw --pack . --profile node16"
},
"dependencies": {
"@ag-ui/langgraph": "0.0.39",
"@ag-ui/langgraph": "0.0.41",
"@copilotkit/shared": "workspace:*"
},
"devDependencies": {
Expand Down
13 changes: 8 additions & 5 deletions packages/sdk-js/src/langgraph/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
StandardSchemaV1,
} from "@standard-schema/spec";
import * as z from "zod";
import { getA2UITools } from "@ag-ui/langgraph";
import { getA2UITools, type A2UIToolParams } from "@ag-ui/langgraph";
import { getForwardedHeaders } from "../header-propagation";

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -394,11 +394,14 @@ const buildMiddlewareInput = (exposeState: ExposeStateOption) => ({
const decision = a2uiInjectDecision(request.state);
if (typeof getA2UITools === "function" && decision) {
const catalog = resolveA2uiCatalog(request.state);
const opts: { defaultCatalogId?: string; compositionGuide?: string } = {};
if (catalog?.catalogId) opts.defaultCatalogId = catalog.catalogId;
// Shared A2UIToolParams: a single params object owned by the toolkit.
// `model` lives inside it; `compositionGuide` is folded into the
// `guidelines` bag alongside generation/design overrides.
const params: A2UIToolParams = { model: request.model };
if (catalog?.catalogId) params.defaultCatalogId = catalog.catalogId;
if (catalog?.compositionGuide)
opts.compositionGuide = catalog.compositionGuide;
const candidate = getA2UITools(request.model, opts);
params.guidelines = { compositionGuide: catalog.compositionGuide };
const candidate = getA2UITools(params);
const existingNames = new Set(
(request.tools || []).map((t: any) => t?.name),
);
Expand Down
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions sdk-python/copilotkit/copilotkit_lg_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
# Guarded so an older/skewed version without the factory degrades to
# "no auto-A2UI" instead of breaking the whole middleware import.
try: # pragma: no cover - exercised indirectly via the a2ui injection path
from ag_ui_langgraph import get_a2ui_tools
from ag_ui_langgraph import get_a2ui_tools, A2UIToolParams
except Exception: # noqa: BLE001 - any import failure means the feature is off
get_a2ui_tools = None
A2UIToolParams = None

# Track which httpx clients already have the header-propagation hook installed
# (by object id) so we never double-install on repeated model calls.
Expand Down Expand Up @@ -420,15 +421,18 @@ def _maybe_build_a2ui_tool(self, request: ModelRequest) -> Any | None:
resolved = self._resolve_a2ui_catalog(state)
component_schema, catalog_id = resolved if resolved else (None, None)

kwargs: dict[str, Any] = {}
# Shared A2UIToolParams: a single params object owned by the toolkit.
# ``model`` lives inside it; ``composition_guide`` is folded into the
# ``guidelines`` bag alongside generation/design overrides.
params: "A2UIToolParams" = {"model": request.model}
if catalog_id:
kwargs["default_catalog_id"] = catalog_id
params["default_catalog_id"] = catalog_id
# Feed the registered component schema to the subagent so it composes
# only catalog components (the toolkit appends this to its prompt).
if component_schema:
kwargs["composition_guide"] = component_schema
params["guidelines"] = {"composition_guide": component_schema}

tool = get_a2ui_tools(request.model, **kwargs)
tool = get_a2ui_tools(params)

# (2) Don't double-inject if the agent already defines this tool.
existing_names = {getattr(t, "name", None) for t in (request.tools or [])}
Expand Down
22 changes: 11 additions & 11 deletions sdk-python/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk-python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ python = ">=3.10,<3.15"
langgraph = { version = ">=0.3.25,<2" }
langchain = { version = ">=0.3.0" }
crewai = { version = ">=0.118.0", optional = true, python = ">=3.10,<3.14" }
ag-ui-langgraph = { version = ">=0.0.40", extras = ["fastapi"] }
ag-ui-langgraph = { version = ">=0.0.41", extras = ["fastapi"] }
ag-ui-protocol = ">=0.1.15"
fastapi = ">=0.115.0,<1.0.0"
partialjson = "^0.0.8"
Expand Down
5 changes: 4 additions & 1 deletion sdk-python/tests/test_copilotkit_lg_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,10 @@ def test_none_configurable_falls_back_to_context(self, monkeypatch):

_a2ui_unavailable = pytest.mark.skipif(
get_a2ui_tools is None,
reason="ag-ui-langgraph without get_a2ui_tools (needs >=0.0.37)",
reason=(
"ag-ui-langgraph without get_a2ui_tools; the single-arg "
"A2UIToolParams API needs the OSS-248 release (>=0.0.41)"
),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ const searchFlights = tool(
// a2ui_dynamic). A secondary LLM designs the surface; the factory forces the
// host catalog and emits the a2ui_operations envelope. Replaces the prior
// hand-rolled generate_a2ui tool.
const generateA2ui = getA2UITools(new ChatOpenAI({ model: "gpt-4.1" }), {
const generateA2ui = getA2UITools({
model: new ChatOpenAI({ model: "gpt-4.1" }),
defaultCatalogId: "copilotkit://app-dashboard-catalog",
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ base_model = ChatOpenAI(model="gpt-4o")

TOOLS = [
get_a2ui_tools(
model=base_model,
default_catalog_id="https://a2ui.org/demos/dojo/dynamic_catalog.json",
composition_guide=COMPOSITION_GUIDE, # how the subagent should use your components
{
"model": base_model,
"default_catalog_id": "https://a2ui.org/demos/dojo/dynamic_catalog.json",
# guidelines: optional generation/design/composition prompt knobs
"guidelines": {"composition_guide": COMPOSITION_GUIDE},
}
)
]

Expand All @@ -91,11 +94,11 @@ workflow.add_edge("tool_node", "chat_node")
graph = workflow.compile()
```

`get_a2ui_tools(model, default_catalog_id=..., composition_guide=...)` is the same factory the middleware uses under the hood:
`get_a2ui_tools(params)` takes a single `A2UIToolParams` object — the same factory the middleware uses under the hood:

- **`model`** — the secondary LLM that designs the surface.
- **`default_catalog_id`** — binds generated surfaces to your catalog (BYOC). Catalog ownership stays with the host; the model never picks it.
- **`composition_guide`** — optional rules appended to the subagent's prompt telling it which components exist and how to compose them.
- **`guidelines`** — optional prompt knobs: `composition_guide` (which components exist and how to compose them), plus `generation_guidelines` / `design_guidelines` to override the built-in generation/design defaults.

## Progressive streaming

Expand Down
Loading