Skip to content

Commit 9953b8a

Browse files
authored
Merge branch 'main' into worktree-lucky-popping-wren
2 parents 6589cf2 + e5daf8d commit 9953b8a

8 files changed

Lines changed: 197 additions & 142 deletions

File tree

examples/integrations/langgraph-python/apps/app/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
"start": "next start"
99
},
1010
"dependencies": {
11-
"@copilotkit/a2ui-renderer": "1.55.2",
12-
"@copilotkit/react-core": "1.55.2",
13-
"@copilotkit/react-ui": "1.55.2",
14-
"@copilotkit/runtime": "1.55.2",
15-
"@copilotkit/shared": "1.55.2",
11+
"@copilotkit/a2ui-renderer": "https://pkg.pr.new/CopilotKit/CopilotKit/@copilotkit/a2ui-renderer@cfeb1c68c43e1c0fc00bcef91ea48b065668218b",
12+
"@copilotkit/react-core": "https://pkg.pr.new/CopilotKit/CopilotKit/@copilotkit/react-core@cfeb1c68c43e1c0fc00bcef91ea48b065668218b",
13+
"@copilotkit/react-ui": "https://pkg.pr.new/CopilotKit/CopilotKit/@copilotkit/react-ui@cfeb1c68c43e1c0fc00bcef91ea48b065668218b",
14+
"@copilotkit/runtime": "https://pkg.pr.new/CopilotKit/CopilotKit/@copilotkit/runtime@cfeb1c68c43e1c0fc00bcef91ea48b065668218b",
15+
"@copilotkit/shared": "https://pkg.pr.new/CopilotKit/CopilotKit/@copilotkit/shared@cfeb1c68c43e1c0fc00bcef91ea48b065668218b",
1616
"@radix-ui/react-checkbox": "^1.3.3",
1717
"@radix-ui/react-label": "^2.1.8",
1818
"@radix-ui/react-separator": "^1.1.8",

examples/integrations/langgraph-python/pnpm-lock.yaml

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

packages/a2ui-renderer/src/react-renderer/catalog-utils.ts

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const BASIC_CATALOG_ID =
1111
* a frontend-provided schema with a server-side one.
1212
*/
1313
export const A2UI_SCHEMA_CONTEXT_DESCRIPTION =
14-
"A2UI Component Schema — available components for generating UI surfaces. Use these component names and props when creating A2UI operations.";
14+
"A2UI Component Schema — available components for generating UI surfaces. Use these component names and properties when creating A2UI operations.";
1515

1616
/**
1717
* Check whether a catalog is a superset of the basic catalog
@@ -85,28 +85,54 @@ export function buildCatalogContextValue(
8585
}
8686

8787
/**
88-
* Extract component schemas from a catalog in the same format used by
89-
* the A2UI middleware (`A2UIComponentSchema[]`). Each component's Zod
90-
* schema is converted to JSON Schema via zod-to-json-schema.
88+
* A2UI v0.9 inline catalog format — matches the structure defined by the
89+
* A2UI specification (basic_catalog.json). Each component is keyed by
90+
* name and uses `allOf` to compose ComponentCommon with component-specific
91+
* properties so the schema mirrors the flat wire format the LLM must produce.
92+
*/
93+
export interface InlineCatalogSchema {
94+
catalogId: string;
95+
components: Record<string, Record<string, unknown>>;
96+
}
97+
98+
/**
99+
* Extract component schemas from a catalog in the A2UI v0.9 inline catalog
100+
* format. This mirrors `generateInlineCatalog` from `@a2ui/web_core` so
101+
* the schema the LLM sees matches the spec and the flat wire format:
102+
*
103+
* { "Column": { "allOf": [
104+
* { "$ref": "common_types.json#/$defs/ComponentCommon" },
105+
* { "properties": { "component": {"const":"Column"}, "gap": ..., "children": ... },
106+
* "required": ["component"] }
107+
* ]}}
91108
*
92109
* When sent via `useAgentContext` with `A2UI_SCHEMA_CONTEXT_DESCRIPTION`,
93110
* the middleware can optionally overwrite it with a server-side schema.
94111
*/
95112
export function extractCatalogComponentSchemas(
96113
catalog?: Catalog<ComponentApi>,
97-
): Array<{ name: string; props: Record<string, unknown> }> {
114+
): InlineCatalogSchema {
98115
const resolved = catalog ?? basicCatalog;
99-
const schemas: Array<{ name: string; props: Record<string, unknown> }> = [];
116+
const components: Record<string, Record<string, unknown>> = {};
100117

101118
for (const [name, comp] of resolved.components) {
102-
schemas.push({
103-
name,
104-
props: zodToJsonSchema(comp.schema, { target: "openApi3" }) as Record<
105-
string,
106-
unknown
107-
>,
108-
});
119+
const zodSchema = zodToJsonSchema(comp.schema, {
120+
target: "jsonSchema2019-09",
121+
}) as { properties?: Record<string, unknown>; required?: string[] };
122+
123+
components[name] = {
124+
allOf: [
125+
{ $ref: "common_types.json#/$defs/ComponentCommon" },
126+
{
127+
properties: {
128+
component: { const: name },
129+
...(zodSchema.properties ?? {}),
130+
},
131+
required: ["component", ...(zodSchema.required ?? [])],
132+
},
133+
],
134+
};
109135
}
110136

111-
return schemas;
137+
return { catalogId: resolved.id, components };
112138
}

packages/a2ui-renderer/src/react-renderer/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export {
3434
buildCatalogContextValue,
3535
extractCatalogComponentSchemas,
3636
} from "./catalog-utils";
37+
export type { InlineCatalogSchema } from "./catalog-utils";
3738

3839
// Catalog creation — new API (definitions + renderers)
3940
export { createCatalog, extractSchema } from "./create-catalog";

packages/runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"attw": "attw --pack . --profile node16"
8989
},
9090
"dependencies": {
91-
"@ag-ui/a2ui-middleware": "0.0.4",
91+
"@ag-ui/a2ui-middleware": "0.0.5",
9292
"@ag-ui/client": "0.0.52",
9393
"@ag-ui/core": "0.0.52",
9494
"@ag-ui/encoder": "0.0.52",

packages/shared/src/a2ui-prompts.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,38 @@ CRITICAL: Do NOT use "/name" (absolute) inside templates — use "name" (relativ
4747
The container's path ("/items") uses a leading slash (absolute), but all
4848
components INSIDE the template use paths WITHOUT leading slash.
4949
50-
DATA MODEL:
51-
The "data" key in the tool args is a plain JSON object that initializes the surface
52-
data model. Components bound to paths (e.g. "value": { "path": "/form/name" })
53-
read from and write to this data model. Examples:
54-
For forms: "data": { "form": { "name": "Alice", "email": "" } }
55-
For lists: "data": { "items": [{"name": "Product A"}, {"name": "Product B"}] }
56-
For mixed: "data": { "form": { "query": "" }, "results": [...] }
50+
COMPONENT VALUES — DEFAULT RULE:
51+
Use inline literal values for ALL component properties. Pass strings, numbers,
52+
arrays, and objects directly on the component. Do NOT use { "path": "..." }
53+
objects unless the property's schema explicitly allows it (see exception below).
54+
CRITICAL: USING { "path": "..." } ON A PROPERTY THAT DOES NOT DECLARE PATH
55+
SUPPORT IN ITS SCHEMA WILL CAUSE A RUNTIME CRASH AND BREAK THE ENTIRE UI.
56+
ALWAYS CHECK THE COMPONENT SCHEMA FIRST — IF THE PROPERTY ONLY ACCEPTS A
57+
PLAIN TYPE, YOU MUST USE A LITERAL VALUE.
58+
VERY IMPORTANT: THE APPLICATION WILL BREAK IF YOU DO NOT FOLLOW THIS RULE!
5759
58-
FORMS AND TWO-WAY DATA BINDING:
59-
To create editable forms, bind input components to data model paths using { "path": "..." }.
60-
The client automatically writes user input back to the data model at the bound path.
61-
CRITICAL: Using a literal value (e.g. "value": "") makes the field READ-ONLY.
62-
You MUST use { "path": "..." } to make inputs editable.
60+
For example, a chart's "data" must always be an inline array:
61+
"data": [{"label": "Jan", "value": 100}, {"label": "Feb", "value": 200}]
62+
A metric's "value" must always be an inline string:
63+
"value": "$1,200"
6364
64-
Input components use "value" as the binding property:
65-
"value": { "path": "/form/fieldName" }
65+
PATH BINDING EXCEPTION — SCHEMA-DRIVEN:
66+
A few properties accept { "path": "/some/path" } as an alternative to a literal
67+
value. You can identify these in the Available Components schema: the property
68+
will list BOTH a literal type AND an object-with-path option. If a property only
69+
shows a single type (string, number, array, etc.), it does NOT support path
70+
binding — use a literal value only.
6671
67-
To retrieve form values when a button is clicked, include "context" with path references
68-
in the button's action. Paths are resolved to their current values at click time:
69-
"action": { "event": { "name": "submit", "context": { "userName": { "path": "/form/name" } } } }
72+
Path binding is typically used for editable form inputs so the client can write
73+
user input back to the data model. When building forms:
74+
- Bind input "value" to a data model path: "value": { "path": "/form/name" }
75+
- Pre-fill via the "data" tool argument: "data": { "form": { "name": "Alice" } }
76+
- Capture values on submit via button action context:
77+
"action": { "event": { "name": "submit", "context": { "name": { "path": "/form/name" } } } }
7078
71-
To pre-fill form values, pass initial data via the "data" tool argument:
72-
"data": { "form": { "name": "Markus" } }`;
79+
REPEATING CONTENT uses a structural children format (not the same as value binding):
80+
children: { componentId: "card-id", path: "/items" }
81+
Components inside templates use RELATIVE paths (no leading slash): { "path": "name" }.`;
7382

7483
/**
7584
* Design guidelines — visual design rules, component hierarchy tips,
@@ -94,8 +103,8 @@ Design principles:
94103
"action": { "event": { "name": "myAction", "context": { "key": "value" } } }
95104
The "event" key holds an OBJECT with "name" (required) and "context" (optional).
96105
Do NOT use a flat format like {"event": "name"} — "event" must be an object.
97-
- For forms: every input MUST use path binding on the "value" property
98-
(e.g. "value": { "path": "/form/name" }) to be editable. The submit button's
99-
action context MUST reference the same paths to capture the user's input.
106+
- For forms: check the component schema — if an input's "value" property
107+
supports path binding, use it for editable fields. The submit button's
108+
action context should reference the same paths to capture user input.
100109
101110
Use the SAME surfaceId as the main surface. Match action names to button action event names.`;

pnpm-lock.yaml

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

sdk-python/copilotkit/a2ui.py

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -133,45 +133,38 @@ def render(
133133
components INSIDE the template card use paths WITHOUT leading slash.
134134
Do NOT use "/items/0/name" or "/items/{@key}/name" — just "name".
135135
136-
DATA MODEL:
137-
The "data" key in the tool args is a plain JSON object that initializes the surface
138-
data model. Components bound to paths (e.g. "value": { "path": "/form/name" })
139-
read from and write to this data model. Examples:
140-
For forms: "data": { "form": { "name": "Alice", "email": "" } }
141-
For lists: "data": { "items": [{"name": "Product A"}, {"name": "Product B"}] }
142-
For mixed: "data": { "form": { "query": "" }, "results": [...] }
143-
144-
FORMS AND TWO-WAY DATA BINDING:
145-
To create editable forms, bind input components to data model paths using { "path": "..." }.
146-
The client automatically writes user input back to the data model at the bound path.
147-
CRITICAL: Using a literal value (e.g. "value": "") makes the field READ-ONLY.
148-
You MUST use { "path": "..." } to make inputs editable.
149-
150-
All input components use "value" as the binding property:
151-
- TextField: "value": { "path": "/form/fieldName" }
152-
- CheckBox: "value": { "path": "/form/isChecked" }
153-
- Slider: "value": { "path": "/form/sliderVal" }
154-
- DateTimeInput: "value": { "path": "/form/date" }
155-
- ChoicePicker: "value": { "path": "/form/choices" }
156-
157-
To retrieve form values when a button is clicked, include "context" with path references
158-
in the button's action. Paths are resolved to their current values at click time:
159-
"action": { "event": { "name": "submit", "context": { "userName": { "path": "/form/name" } } } }
160-
161-
To pre-fill form values, pass initial data via the "data" tool argument:
162-
"data": { "form": { "name": "Markus" } }
163-
164-
FORM EXAMPLE (editable text field with pre-filled value + submit button):
165-
"components": [
166-
{ "id": "root", "component": "Card", "child": "form-col" },
167-
{ "id": "form-col", "component": "Column", "children": ["name-field", "submit-row"] },
168-
{ "id": "name-field", "component": "TextField", "label": "Name", "value": { "path": "/form/name" } },
169-
{ "id": "submit-row", "component": "Row", "justify": "end", "children": ["submit-btn"] },
170-
{ "id": "submit-btn", "component": "Button", "child": "btn-text", "variant": "primary",
171-
"action": { "event": { "name": "submit", "context": { "userName": { "path": "/form/name" } } } } },
172-
{ "id": "btn-text", "component": "Text", "text": "Submit" }
173-
],
174-
"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" }."""
175168

176169
DEFAULT_DESIGN_GUIDELINES = """\
177170
Create polished, visually appealing interfaces:

0 commit comments

Comments
 (0)