@copilotkit/react-core 1.80.0 → 1.82.0
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.
- package/dist/MarkdownSurfaces-DGpjmPaB.mjs +3531 -0
- package/dist/MarkdownSurfaces-DGpjmPaB.mjs.map +1 -0
- package/dist/MarkdownSurfaces-FF5za2kU.cjs +3816 -0
- package/dist/MarkdownSurfaces-FF5za2kU.cjs.map +1 -0
- package/dist/{copilotkit-CWOSXhRO.d.mts → copilotkit-C765tOUi.d.mts} +70 -36
- package/dist/copilotkit-C765tOUi.d.mts.map +1 -0
- package/dist/{copilotkit-DKICi3nC.cjs → copilotkit-CVvbU9lV.cjs} +2162 -675
- package/dist/copilotkit-CVvbU9lV.cjs.map +1 -0
- package/dist/{copilotkit-BNLtgOyD.mjs → copilotkit-DDReN1Px.mjs} +2106 -568
- package/dist/copilotkit-DDReN1Px.mjs.map +1 -0
- package/dist/{copilotkit-Dfw9HzO9.d.cts → copilotkit-wqaNanhw.d.cts} +70 -36
- package/dist/copilotkit-wqaNanhw.d.cts.map +1 -0
- package/dist/index.cjs +8 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +4474 -11
- package/dist/index.umd.js.map +1 -1
- package/dist/runtime-context-CrHq-iTf.d.mts +924 -0
- package/dist/runtime-context-CrHq-iTf.d.mts.map +1 -0
- package/dist/runtime-context-D3NcP9oE.d.cts +924 -0
- package/dist/runtime-context-D3NcP9oE.d.cts.map +1 -0
- package/dist/v2/generative-ui.cjs +8154 -0
- package/dist/v2/generative-ui.cjs.map +1 -0
- package/dist/v2/generative-ui.d.cts +4950 -0
- package/dist/v2/generative-ui.d.cts.map +1 -0
- package/dist/v2/generative-ui.d.mts +4950 -0
- package/dist/v2/generative-ui.d.mts.map +1 -0
- package/dist/v2/generative-ui.mjs +8140 -0
- package/dist/v2/generative-ui.mjs.map +1 -0
- package/dist/v2/headless.cjs +10 -1
- package/dist/v2/headless.cjs.map +1 -1
- package/dist/v2/headless.d.cts +9 -0
- package/dist/v2/headless.d.cts.map +1 -1
- package/dist/v2/headless.d.mts +9 -0
- package/dist/v2/headless.d.mts.map +1 -1
- package/dist/v2/headless.mjs +10 -1
- package/dist/v2/headless.mjs.map +1 -1
- package/dist/v2/index.cjs +65 -4
- package/dist/v2/index.css +1 -1
- package/dist/v2/index.d.cts +3 -2
- package/dist/v2/index.d.mts +3 -2
- package/dist/v2/index.mjs +3 -2
- package/dist/v2/index.umd.js +4962 -236
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +21 -8
- package/dist/copilotkit-BNLtgOyD.mjs.map +0 -1
- package/dist/copilotkit-CWOSXhRO.d.mts.map +0 -1
- package/dist/copilotkit-DKICi3nC.cjs.map +0 -1
- package/dist/copilotkit-Dfw9HzO9.d.cts.map +0 -1
package/dist/index.umd.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
(function(global, factory) {
|
|
4
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('@copilotkit/core'), require('@ag-ui/client'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@ag-ui/core'), require('zod'), require('@copilotkit/mcp-apps-renderer/activity'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('react-dom'), require('react-markdown'), require('@copilotkit/runtime-client-gql')) :
|
|
5
|
-
typeof define === 'function' && define.amd ? define(['exports', 'react', '@copilotkit/core', '@ag-ui/client', '@copilotkit/shared', 'react/jsx-runtime', '@ag-ui/core', 'zod', '@copilotkit/mcp-apps-renderer/activity', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', 'react-dom', 'react-markdown', '@copilotkit/runtime-client-gql'], factory) :
|
|
6
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCore = {}), global.React,global.CopilotKitCore,global.AgUIClient,global.CopilotKitShared,global.ReactJsxRuntime,global._ag_ui_core,global.Zod,global.CopilotKitMcpAppsRendererActivity,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global.ReactDOM,global.ReactMarkdown,global.CopilotKitRuntimeClientGQL));
|
|
7
|
-
})(this, function(exports, react, _copilotkit_core, _ag_ui_client, _copilotkit_shared, react_jsx_runtime, _ag_ui_core, zod, _copilotkit_mcp_apps_renderer_activity, _copilotkit_a2ui_renderer, zod_to_json_schema, react_dom, react_markdown, _copilotkit_runtime_client_gql) {
|
|
4
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('@copilotkit/core'), require('@ag-ui/client'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@ag-ui/core'), require('zod'), require('@copilotkit/mcp-apps-renderer/activity'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('@a2ui/web_core/v0_9'), require('react-dom'), require('react-markdown'), require('@copilotkit/runtime-client-gql')) :
|
|
5
|
+
typeof define === 'function' && define.amd ? define(['exports', 'react', '@copilotkit/core', '@ag-ui/client', '@copilotkit/shared', 'react/jsx-runtime', '@ag-ui/core', 'zod', '@copilotkit/mcp-apps-renderer/activity', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', '@a2ui/web_core/v0_9', 'react-dom', 'react-markdown', '@copilotkit/runtime-client-gql'], factory) :
|
|
6
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCore = {}), global.React,global.CopilotKitCore,global.AgUIClient,global.CopilotKitShared,global.ReactJsxRuntime,global._ag_ui_core,global.Zod,global.CopilotKitMcpAppsRendererActivity,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global._a2ui_web_core_v0_9,global.ReactDOM,global.ReactMarkdown,global.CopilotKitRuntimeClientGQL));
|
|
7
|
+
})(this, function(exports, react, _copilotkit_core, _ag_ui_client, _copilotkit_shared, react_jsx_runtime, _ag_ui_core, zod, _copilotkit_mcp_apps_renderer_activity, _copilotkit_a2ui_renderer, zod_to_json_schema, _a2ui_web_core_v0_9, react_dom, react_markdown, _copilotkit_runtime_client_gql) {
|
|
8
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
9
9
|
//#region \0rolldown/runtime.js
|
|
10
10
|
var __create = Object.create;
|
|
@@ -240,7 +240,16 @@ react_markdown = __toESM(react_markdown);
|
|
|
240
240
|
chatToggleCloseLabel: "Close chat",
|
|
241
241
|
chatScrollToBottomLabel: "Scroll to bottom",
|
|
242
242
|
modalHeaderTitle: "CopilotKit Chat",
|
|
243
|
-
welcomeMessageText: "How can I help you today?"
|
|
243
|
+
welcomeMessageText: "How can I help you today?",
|
|
244
|
+
reasoningMessageThinkingLabel: "Thinking…",
|
|
245
|
+
chatInputSlashMenuLabel: "Slash commands",
|
|
246
|
+
chatInputSlashMenuEmptyText: "No commands found",
|
|
247
|
+
attachmentRemoveButtonLabel: "Remove attachment",
|
|
248
|
+
attachmentPlayVideoButtonLabel: "Play video",
|
|
249
|
+
attachmentPreviewCloseButtonLabel: "Close preview",
|
|
250
|
+
modalHeaderCloseButtonLabel: "Close",
|
|
251
|
+
modalHeaderThreadsButtonLabel: "Open threads",
|
|
252
|
+
sidebarLabel: "Copilot chat sidebar"
|
|
244
253
|
};
|
|
245
254
|
/**
|
|
246
255
|
* Mobile breakpoint below which the chat modal and the thread-list drawer are
|
|
@@ -615,7 +624,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
615
624
|
|
|
616
625
|
//#endregion
|
|
617
626
|
//#region package.json
|
|
618
|
-
var version = "1.
|
|
627
|
+
var version = "1.82.0";
|
|
619
628
|
|
|
620
629
|
//#endregion
|
|
621
630
|
//#region src/v2/components/CopilotKitInspector.tsx
|
|
@@ -1888,7 +1897,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
1888
1897
|
const groupedOperations = (0, react.useMemo)(() => {
|
|
1889
1898
|
const groups = /* @__PURE__ */ new Map();
|
|
1890
1899
|
for (const operation of operations) {
|
|
1891
|
-
const surfaceId = getOperationSurfaceId(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
|
|
1900
|
+
const surfaceId = getOperationSurfaceId$1(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
|
|
1892
1901
|
if (!groups.has(surfaceId)) groups.set(surfaceId, []);
|
|
1893
1902
|
groups.get(surfaceId).push(operation);
|
|
1894
1903
|
}
|
|
@@ -2110,7 +2119,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2110
2119
|
* @param operation - One A2UI operation, of any shape.
|
|
2111
2120
|
* @returns The surface id, or null when the operation names none.
|
|
2112
2121
|
*/
|
|
2113
|
-
function getOperationSurfaceId(operation) {
|
|
2122
|
+
function getOperationSurfaceId$1(operation) {
|
|
2114
2123
|
if (!operation || typeof operation !== "object") return null;
|
|
2115
2124
|
const nested = operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId;
|
|
2116
2125
|
if (typeof nested === "string") return nested;
|
|
@@ -2181,7 +2190,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2181
2190
|
*/
|
|
2182
2191
|
function A2UICatalogContext({ catalog, includeSchema }) {
|
|
2183
2192
|
const { copilotkit } = useCopilotKit();
|
|
2184
|
-
const capabilitiesValue = (0, react.useMemo)(() => (0, _copilotkit_a2ui_renderer.buildCatalogContextValue)(catalog), [catalog]);
|
|
2193
|
+
const capabilitiesValue = (0, react.useMemo)(() => (0, _copilotkit_a2ui_renderer.buildCatalogContextValue)(catalog, { includeSchema: includeSchema !== false }), [catalog, includeSchema]);
|
|
2185
2194
|
const schemaValue = (0, react.useMemo)(() => includeSchema !== false ? JSON.stringify((0, _copilotkit_a2ui_renderer.extractCatalogComponentSchemas)(catalog)) : null, [catalog, includeSchema]);
|
|
2186
2195
|
const a2uiAgentsKey = copilotkit?.a2uiAgents?.join(",");
|
|
2187
2196
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -2223,6 +2232,4452 @@ react_markdown = __toESM(react_markdown);
|
|
|
2223
2232
|
return null;
|
|
2224
2233
|
}
|
|
2225
2234
|
|
|
2235
|
+
//#endregion
|
|
2236
|
+
//#region src/v2/generative-ui/action-message.ts
|
|
2237
|
+
/** Context keys that supply the message's first line, in priority order. */
|
|
2238
|
+
const TEXT_KEYS = ["message", "label"];
|
|
2239
|
+
function isNonEmptyString(value) {
|
|
2240
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2241
|
+
}
|
|
2242
|
+
/** `choose_plan`, `choose-plan` and `choosePlan` all become "Choose plan". */
|
|
2243
|
+
function humanize(name) {
|
|
2244
|
+
const spaced = name.replace(/[_-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
|
|
2245
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
2246
|
+
}
|
|
2247
|
+
function formatContextValue(value) {
|
|
2248
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Turns a user event that no function handles into the text of a visible
|
|
2252
|
+
* user message.
|
|
2253
|
+
*
|
|
2254
|
+
* The first line is, in order: `userMessage`, `context.message`,
|
|
2255
|
+
* `context.label`, the label of the source component (from `resolveLabel`),
|
|
2256
|
+
* or the event name made readable (`choose_plan` becomes "Choose plan").
|
|
2257
|
+
* Each other context field follows as one `key: value` line. Objects are
|
|
2258
|
+
* written as JSON. `null` and `undefined` fields are skipped.
|
|
2259
|
+
*
|
|
2260
|
+
* @param event - The user event, as sent by a surface.
|
|
2261
|
+
* @param resolveLabel - Finds the visible label of a component by id. Build
|
|
2262
|
+
* one with {@link createComponentLabelResolver}.
|
|
2263
|
+
*
|
|
2264
|
+
* @example
|
|
2265
|
+
* ```ts
|
|
2266
|
+
* buildActionMessageText({
|
|
2267
|
+
* name: "choose_plan",
|
|
2268
|
+
* surfaceId: "plans",
|
|
2269
|
+
* componentId: "team-button",
|
|
2270
|
+
* userMessage: "I pick the Team plan",
|
|
2271
|
+
* context: { seats: 12 },
|
|
2272
|
+
* });
|
|
2273
|
+
* // "I pick the Team plan\n\nseats: 12"
|
|
2274
|
+
* ```
|
|
2275
|
+
*/
|
|
2276
|
+
function buildActionMessageText(event, resolveLabel) {
|
|
2277
|
+
const { context, userMessage } = event;
|
|
2278
|
+
const explicit = [userMessage, ...TEXT_KEYS.map((key) => context[key])].find(isNonEmptyString);
|
|
2279
|
+
const resolved = explicit ? void 0 : resolveLabel?.(event.componentId);
|
|
2280
|
+
const head = explicit ?? (isNonEmptyString(resolved) ? resolved.trim() : humanize(event.name));
|
|
2281
|
+
const lines = Object.entries(context).filter(([key, value]) => !TEXT_KEYS.includes(key) && value !== void 0 && value !== null).map(([key, value]) => `${key}: ${formatContextValue(value)}`);
|
|
2282
|
+
return lines.length > 0 ? `${head}\n\n${lines.join("\n")}` : head;
|
|
2283
|
+
}
|
|
2284
|
+
function ownText(component) {
|
|
2285
|
+
if (!component) return void 0;
|
|
2286
|
+
if (isNonEmptyString(component.label)) return component.label;
|
|
2287
|
+
if (isNonEmptyString(component.text)) return component.text;
|
|
2288
|
+
}
|
|
2289
|
+
function childIds(component) {
|
|
2290
|
+
const { child, children } = component;
|
|
2291
|
+
const single = typeof child === "string" ? [child] : [];
|
|
2292
|
+
const multiple = Array.isArray(children) ? children.filter((id) => typeof id === "string") : [];
|
|
2293
|
+
return [...single, ...multiple];
|
|
2294
|
+
}
|
|
2295
|
+
/** The components that an operation adds, inline or in an update. */
|
|
2296
|
+
function componentsOf(operation) {
|
|
2297
|
+
if ("createSurface" in operation) return operation.createSurface.components ?? [];
|
|
2298
|
+
if ("updateComponents" in operation) return operation.updateComponents.components;
|
|
2299
|
+
return [];
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Builds a function that finds the visible label of a component on a surface.
|
|
2303
|
+
*
|
|
2304
|
+
* The label is the component's own string `label`, else its string `text`,
|
|
2305
|
+
* else the first `child` or `children` component that has a string `label`
|
|
2306
|
+
* or `text`. When a component id is defined more than once, the last
|
|
2307
|
+
* definition wins, as in the renderer.
|
|
2308
|
+
*
|
|
2309
|
+
* @param operations - The committed operations of the surface.
|
|
2310
|
+
*
|
|
2311
|
+
* @example
|
|
2312
|
+
* ```ts
|
|
2313
|
+
* const resolveLabel = createComponentLabelResolver(surface.operations);
|
|
2314
|
+
* buildActionMessageText(event, resolveLabel);
|
|
2315
|
+
* ```
|
|
2316
|
+
*/
|
|
2317
|
+
function createComponentLabelResolver(operations) {
|
|
2318
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2319
|
+
for (const operation of operations) {
|
|
2320
|
+
const components = componentsOf(operation);
|
|
2321
|
+
for (const component of components) byId.set(component.id, component);
|
|
2322
|
+
}
|
|
2323
|
+
return (componentId) => {
|
|
2324
|
+
const component = byId.get(componentId);
|
|
2325
|
+
if (!component) return void 0;
|
|
2326
|
+
const own = ownText(component);
|
|
2327
|
+
if (own) return own;
|
|
2328
|
+
const ids = childIds(component);
|
|
2329
|
+
for (const id of ids) {
|
|
2330
|
+
const text = ownText(byId.get(id));
|
|
2331
|
+
if (text) return text;
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
//#endregion
|
|
2337
|
+
//#region src/v2/generative-ui/json-schema.ts
|
|
2338
|
+
/**
|
|
2339
|
+
* JSON Schema for a Standard Schema (zod v3 via zod-to-json-schema),
|
|
2340
|
+
* inlined, no `$schema`. A prop marked by `child()` or `childList()`
|
|
2341
|
+
* becomes an A2UI `$ref` to `ComponentId` or `ChildList`.
|
|
2342
|
+
*/
|
|
2343
|
+
function toJsonSchema(schema) {
|
|
2344
|
+
const { $schema: _omit, ...rest } = (0, _copilotkit_shared.schemaToJsonSchema)(schema, { zodToJsonSchema: (s) => (0, zod_to_json_schema.zodToJsonSchema)(s, { $refStrategy: "none" }) });
|
|
2345
|
+
return (0, _copilotkit_shared.applyChildRefMarks)(rest);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
//#endregion
|
|
2349
|
+
//#region src/v2/generative-ui/catalog.ts
|
|
2350
|
+
/** The A2UI component envelope: `id`, `component`, `catalogId`, `accessibility`, `metadata`. */
|
|
2351
|
+
const RESERVED_KEYS = new Set([
|
|
2352
|
+
"id",
|
|
2353
|
+
"component",
|
|
2354
|
+
..._copilotkit_shared.A2UI_COMPONENT_ENVELOPE_KEYS
|
|
2355
|
+
]);
|
|
2356
|
+
/** The id of the catalog that declared each component, for the inspector. */
|
|
2357
|
+
const componentCatalogs = /* @__PURE__ */ new WeakMap();
|
|
2358
|
+
/**
|
|
2359
|
+
* The id of the catalog that declared each component of `ui`. A component
|
|
2360
|
+
* from `extends` keeps the id of its base. Not exported from the package.
|
|
2361
|
+
*/
|
|
2362
|
+
function componentCatalogIds(ui) {
|
|
2363
|
+
return componentCatalogs.get(ui) ?? /* @__PURE__ */ new Map();
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* The catalogs that an operation can name: the id of `ui`, then the ids of
|
|
2367
|
+
* the catalogs in its `extends` that declared a component. Not exported
|
|
2368
|
+
* from the package.
|
|
2369
|
+
*/
|
|
2370
|
+
function catalogIdsOf(ui) {
|
|
2371
|
+
const declared = componentCatalogs.get(ui)?.values() ?? [];
|
|
2372
|
+
return [...new Set([ui.id, ...declared])];
|
|
2373
|
+
}
|
|
2374
|
+
/** The setter prop of a writable prop: `value` gives `setValue`. */
|
|
2375
|
+
function setterNameOf(key) {
|
|
2376
|
+
return `set${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
//#endregion
|
|
2380
|
+
//#region src/v2/generative-ui/manifest.ts
|
|
2381
|
+
/**
|
|
2382
|
+
* The built-in functions (the A2UI basic catalog plus calc and series) as
|
|
2383
|
+
* manifest entries. Their args are described by the short signature, not
|
|
2384
|
+
* by a full JSON Schema: the renderer checks them when they run. An app
|
|
2385
|
+
* function of the same name replaces the entry.
|
|
2386
|
+
*/
|
|
2387
|
+
function builtInFunctionManifests() {
|
|
2388
|
+
return Object.fromEntries(Object.entries(_copilotkit_shared.GENERATIVE_UI_FUNCTIONS).map(([name, fn]) => [name, {
|
|
2389
|
+
description: fn.description,
|
|
2390
|
+
parameters: {
|
|
2391
|
+
type: "object",
|
|
2392
|
+
description: fn.parameters
|
|
2393
|
+
},
|
|
2394
|
+
returnType: fn.returnType,
|
|
2395
|
+
allowedCallers: fn.allowedCallers,
|
|
2396
|
+
...fn.requiresUserActivation ? { requiresUserActivation: true } : {}
|
|
2397
|
+
}]));
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Builds the per-run manifest. Never contains handlers or renderers.
|
|
2401
|
+
*
|
|
2402
|
+
* - `functions`: the built-in functions, then the declared functions with
|
|
2403
|
+
* exactly one mounted handler.
|
|
2404
|
+
* - `targets`: the chat first, then each mounted `<GenerativeUISurface>`,
|
|
2405
|
+
* with its `description` and its `components` when it has them.
|
|
2406
|
+
*/
|
|
2407
|
+
function buildGenerativeUIManifest(runtime, threadId) {
|
|
2408
|
+
const { ui, manager, dispatcher } = runtime;
|
|
2409
|
+
const inlineTargets = manager.getMountedTargets().filter((target) => target.id !== _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET).map(({ id, ...rest }) => ({
|
|
2410
|
+
id,
|
|
2411
|
+
kind: "inline",
|
|
2412
|
+
...rest
|
|
2413
|
+
}));
|
|
2414
|
+
return {
|
|
2415
|
+
catalogId: ui.id,
|
|
2416
|
+
catalogIds: catalogIdsOf(ui),
|
|
2417
|
+
components: ui.manifestComponents,
|
|
2418
|
+
functions: {
|
|
2419
|
+
...Object.fromEntries(Object.entries(builtInFunctionManifests()).filter(([name]) => !ui.functions.has(name))),
|
|
2420
|
+
...dispatcher.listExposed()
|
|
2421
|
+
},
|
|
2422
|
+
targets: [{
|
|
2423
|
+
id: _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET,
|
|
2424
|
+
kind: "chat"
|
|
2425
|
+
}, ...inlineTargets],
|
|
2426
|
+
surfaces: threadId ? manager.describeSurfaces(threadId) : {}
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Builds the envelope that a run sends as `forwardedProps.copilotkit.ui`,
|
|
2431
|
+
* from the per-run manifest. It has the A2UI v1.0 renderer capabilities
|
|
2432
|
+
* shape: one inline catalog with the id of the UI, and the CopilotKit data
|
|
2433
|
+
* in `metadata.extensions`.
|
|
2434
|
+
*
|
|
2435
|
+
* The inline catalog has no `components` only for the markdown transport
|
|
2436
|
+
* with `includeCatalog: false`. It has `instructions` for the markdown
|
|
2437
|
+
* transport, or when given.
|
|
2438
|
+
*
|
|
2439
|
+
* @param runtime - The generative UI runtime of the provider.
|
|
2440
|
+
* @param threadId - The thread of the run. Its surfaces go in `copilotkit_surfaces`.
|
|
2441
|
+
* @param options - The transport and what to send.
|
|
2442
|
+
*
|
|
2443
|
+
* @example
|
|
2444
|
+
* ```ts
|
|
2445
|
+
* const envelope = buildGenerativeUIEnvelope(runtime, agent.threadId, {
|
|
2446
|
+
* transport: "tool",
|
|
2447
|
+
* includeCatalog: true,
|
|
2448
|
+
* contextSent: false,
|
|
2449
|
+
* });
|
|
2450
|
+
* envelope["v1.0"].supportedCatalogIds; // [runtime.ui.id]
|
|
2451
|
+
* ```
|
|
2452
|
+
*/
|
|
2453
|
+
function buildGenerativeUIEnvelope(runtime, threadId, options) {
|
|
2454
|
+
const { transport, includeCatalog, contextSent, action } = options;
|
|
2455
|
+
const manifest = buildGenerativeUIManifest(runtime, threadId);
|
|
2456
|
+
const instructions = options.instructions ?? (transport === "markdown" ? (0, _copilotkit_shared.buildGenerativeUIMarkdownGuidelines)((0, _copilotkit_shared.generativeUIGuideFeatures)(manifest)) : void 0);
|
|
2457
|
+
const omitComponents = transport === "markdown" && !includeCatalog;
|
|
2458
|
+
return {
|
|
2459
|
+
"v1.0": {
|
|
2460
|
+
supportedCatalogIds: manifest.catalogIds ?? [manifest.catalogId],
|
|
2461
|
+
inlineCatalogs: [{
|
|
2462
|
+
catalogId: manifest.catalogId,
|
|
2463
|
+
protocolVersion: "1.0",
|
|
2464
|
+
...omitComponents ? {} : { components: manifest.components },
|
|
2465
|
+
functions: manifest.functions,
|
|
2466
|
+
...instructions === void 0 ? {} : { instructions }
|
|
2467
|
+
}]
|
|
2468
|
+
},
|
|
2469
|
+
metadata: { extensions: {
|
|
2470
|
+
copilotkit_format: _copilotkit_shared.COPILOTKIT_UI_ENVELOPE_FORMAT,
|
|
2471
|
+
copilotkit_transport: transport,
|
|
2472
|
+
copilotkit_targets: manifest.targets,
|
|
2473
|
+
copilotkit_surfaces: manifest.surfaces,
|
|
2474
|
+
copilotkit_contextSent: contextSent,
|
|
2475
|
+
...action === void 0 ? {} : { copilotkit_action: action }
|
|
2476
|
+
} }
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
//#endregion
|
|
2481
|
+
//#region src/v2/generative-ui/is-record.ts
|
|
2482
|
+
/** True for a plain object: not null, not an array. Internal, not exported. */
|
|
2483
|
+
function isRecord(value) {
|
|
2484
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
//#endregion
|
|
2488
|
+
//#region src/v2/generative-ui/markdown/context.ts
|
|
2489
|
+
/** Objects nested deeper than this show as `object`. */
|
|
2490
|
+
const MAX_OBJECT_DEPTH = 2;
|
|
2491
|
+
/** A prop must appear verbatim in at least this many components to be hoisted. */
|
|
2492
|
+
const COMMON_PROP_MIN_COMPONENTS = 8;
|
|
2493
|
+
/** A JSON Schema of a list of schemas (`anyOf`, `oneOf`), else an empty list. */
|
|
2494
|
+
function schemaList(value) {
|
|
2495
|
+
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Puts a top-level union in parentheses, so that `[]` applies to all of
|
|
2499
|
+
* it. A union inside braces needs none.
|
|
2500
|
+
*/
|
|
2501
|
+
function asArrayItem(type) {
|
|
2502
|
+
let outside = type;
|
|
2503
|
+
for (let inner = outside.replace(/\{[^{}]*\}/g, ""); inner !== outside;) {
|
|
2504
|
+
outside = inner;
|
|
2505
|
+
inner = outside.replace(/\{[^{}]*\}/g, "");
|
|
2506
|
+
}
|
|
2507
|
+
return outside.includes(" | ") ? `(${type})[]` : `${type}[]`;
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* A JSON Schema as a short TypeScript-like type, for example
|
|
2511
|
+
* `{ label: string; size?: "sm" | "md" }`.
|
|
2512
|
+
*/
|
|
2513
|
+
function compactType(schema, depth) {
|
|
2514
|
+
const ref = (0, _copilotkit_shared.childRefKind)(schema);
|
|
2515
|
+
if (ref === "single") return "componentId";
|
|
2516
|
+
if (ref === "multiple") return "componentId[] | { componentId: string; path: string }";
|
|
2517
|
+
if ("const" in schema) return JSON.stringify(schema.const);
|
|
2518
|
+
if (Array.isArray(schema.enum)) return schema.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
2519
|
+
const variants = [...schemaList(schema.anyOf), ...schemaList(schema.oneOf)];
|
|
2520
|
+
if (variants.length > 0) return variants.map((variant) => compactType(variant, depth)).join(" | ");
|
|
2521
|
+
return (Array.isArray(schema.type) ? schema.type : [schema.type]).map((type) => {
|
|
2522
|
+
switch (type) {
|
|
2523
|
+
case "string":
|
|
2524
|
+
case "boolean":
|
|
2525
|
+
case "null": return type;
|
|
2526
|
+
case "number":
|
|
2527
|
+
case "integer": return "number";
|
|
2528
|
+
case "array": return isRecord(schema.items) ? asArrayItem(compactType(schema.items, depth)) : "unknown[]";
|
|
2529
|
+
case "object": return compactObject(schema, depth);
|
|
2530
|
+
default: return "unknown";
|
|
2531
|
+
}
|
|
2532
|
+
}).join(" | ");
|
|
2533
|
+
}
|
|
2534
|
+
/** An object schema as `{ key: type; other?: type }`. */
|
|
2535
|
+
function compactObject(schema, depth) {
|
|
2536
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
2537
|
+
const entries = Object.entries(properties);
|
|
2538
|
+
if (entries.length === 0) return isRecord(schema.additionalProperties) ? `Record<string, ${compactType(schema.additionalProperties, depth + 1)}>` : "object";
|
|
2539
|
+
if (depth >= MAX_OBJECT_DEPTH) return "object";
|
|
2540
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
2541
|
+
return `{ ${entries.map(([key, value]) => {
|
|
2542
|
+
const type = isRecord(value) ? compactType(value, depth + 1) : "unknown";
|
|
2543
|
+
return `${key}${required.includes(key) ? "" : "?"}: ${type}`;
|
|
2544
|
+
}).join("; ")} }`;
|
|
2545
|
+
}
|
|
2546
|
+
/** A schema as its type, with its description after `//`. */
|
|
2547
|
+
function describedType(schema, depth) {
|
|
2548
|
+
const type = compactType(schema, depth);
|
|
2549
|
+
return typeof schema.description === "string" ? `${type} // ${schema.description}` : type;
|
|
2550
|
+
}
|
|
2551
|
+
/** The props of a component: `{ "label": "string", "size?": "..." }`. */
|
|
2552
|
+
function compactProps(schema) {
|
|
2553
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
2554
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
2555
|
+
const props = {};
|
|
2556
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
2557
|
+
if (!isRecord(value)) continue;
|
|
2558
|
+
const name = required.includes(key) ? key : `${key}?`;
|
|
2559
|
+
props[name] = describedType(value, 1);
|
|
2560
|
+
}
|
|
2561
|
+
return props;
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* The prop set worth listing once. Candidates are (key, type) pairs that at
|
|
2565
|
+
* least {@link COMMON_PROP_MIN_COMPONENTS} components carry verbatim. The
|
|
2566
|
+
* candidate subset of each component is a possible common set, scored by
|
|
2567
|
+
* the characters it saves (set size times the components that contain all
|
|
2568
|
+
* of it). `undefined` when no set is shared by enough components.
|
|
2569
|
+
*/
|
|
2570
|
+
function findCommonProps(propsList) {
|
|
2571
|
+
const pairKey = (key, type) => `${key}\u0000${type}`;
|
|
2572
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2573
|
+
for (const props of propsList) for (const [key, type] of Object.entries(props)) {
|
|
2574
|
+
const id = pairKey(key, type);
|
|
2575
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
2576
|
+
}
|
|
2577
|
+
let best;
|
|
2578
|
+
let bestScore = 0;
|
|
2579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2580
|
+
for (const props of propsList) {
|
|
2581
|
+
const subset = Object.entries(props).filter(([key, type]) => (counts.get(pairKey(key, type)) ?? 0) >= COMMON_PROP_MIN_COMPONENTS);
|
|
2582
|
+
if (subset.length < 2) continue;
|
|
2583
|
+
const signature = subset.map(([key, type]) => pairKey(key, type)).join("\n");
|
|
2584
|
+
if (seen.has(signature)) continue;
|
|
2585
|
+
seen.add(signature);
|
|
2586
|
+
const members = propsList.filter((other) => subset.every(([key, type]) => other[key] === type)).length;
|
|
2587
|
+
if (members < COMMON_PROP_MIN_COMPONENTS) continue;
|
|
2588
|
+
const score = subset.reduce((total, [key, type]) => total + key.length + type.length, 0) * (members - 1);
|
|
2589
|
+
if (score > bestScore) {
|
|
2590
|
+
bestScore = score;
|
|
2591
|
+
best = Object.fromEntries(subset);
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return best;
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* The component schema of the markdown transport, from the per-run
|
|
2598
|
+
* manifest. It is smaller than the manifest: each prop is one short type,
|
|
2599
|
+
* and the props that many components share verbatim (the layout props of
|
|
2600
|
+
* the containers) are listed once in `commonProps`; a component that takes
|
|
2601
|
+
* them is flagged `includesCommonProps`.
|
|
2602
|
+
* It has the catalog id, the targets with their descriptions and their
|
|
2603
|
+
* component lists, the functions with their parameters, the components
|
|
2604
|
+
* with their props, events and slots, and the surfaces of the thread. A surface lists the
|
|
2605
|
+
* values that the user set and the operations that the client dropped.
|
|
2606
|
+
*
|
|
2607
|
+
* @param manifest - The manifest from `buildGenerativeUIManifest`.
|
|
2608
|
+
* @returns A JSON-ready object.
|
|
2609
|
+
*
|
|
2610
|
+
* @example
|
|
2611
|
+
* ```ts
|
|
2612
|
+
* const value = JSON.stringify(
|
|
2613
|
+
* buildCompactGenerativeUISchema(buildGenerativeUIManifest(runtime, threadId)),
|
|
2614
|
+
* );
|
|
2615
|
+
* ```
|
|
2616
|
+
*/
|
|
2617
|
+
function buildCompactGenerativeUISchema(manifest) {
|
|
2618
|
+
const components = {};
|
|
2619
|
+
for (const [name, component] of Object.entries(manifest.components)) {
|
|
2620
|
+
const events = Object.entries(component.events ?? {}).map(([event, definition]) => [event, definition.description ?? compactType(definition.parameters, 0)]);
|
|
2621
|
+
components[name] = {
|
|
2622
|
+
...component.description ? { description: component.description } : {},
|
|
2623
|
+
props: compactProps(component.props),
|
|
2624
|
+
...events.length > 0 ? { events: Object.fromEntries(events) } : {},
|
|
2625
|
+
...component.slots ? { slots: component.slots } : {}
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
const common = findCommonProps(Object.values(components).map((component) => component.props));
|
|
2629
|
+
if (common) {
|
|
2630
|
+
const commonKeys = Object.keys(common);
|
|
2631
|
+
for (const component of Object.values(components)) {
|
|
2632
|
+
if (!commonKeys.every((key) => component.props[key] === common[key])) continue;
|
|
2633
|
+
component.includesCommonProps = true;
|
|
2634
|
+
component.props = Object.fromEntries(Object.entries(component.props).filter(([key]) => !(key in common)));
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
const functions = Object.fromEntries(Object.entries(manifest.functions).map(([name, fn]) => {
|
|
2638
|
+
const signature = fn.parameters.description;
|
|
2639
|
+
const hasSignature = typeof signature === "string" && signature.startsWith("{") && Object.keys(fn.parameters).length === 2;
|
|
2640
|
+
const callers = (0, _copilotkit_shared.functionCallers)(fn);
|
|
2641
|
+
return [name, {
|
|
2642
|
+
...fn.description ? { description: fn.description } : {},
|
|
2643
|
+
parameters: hasSignature ? signature : compactType(fn.parameters, 0),
|
|
2644
|
+
...callers.agent && _copilotkit_shared.GENERATIVE_UI_AGENT_FUNCTION_CALLS ? { callers: callers.renderer ? "UI or agent" : "agent only" } : {}
|
|
2645
|
+
}];
|
|
2646
|
+
}));
|
|
2647
|
+
return {
|
|
2648
|
+
catalogId: manifest.catalogId,
|
|
2649
|
+
targets: manifest.targets,
|
|
2650
|
+
functions,
|
|
2651
|
+
...common ? { commonProps: common } : {},
|
|
2652
|
+
components,
|
|
2653
|
+
surfaces: manifest.surfaces
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Registers the two context entries of the markdown transport: the compact
|
|
2658
|
+
* component schema and the fence guideline. Both go only to `agentIds`.
|
|
2659
|
+
*
|
|
2660
|
+
* The schema is built again at each run start and when a
|
|
2661
|
+
* `<GenerativeUISurface>` mounts, unmounts, or changes its description or
|
|
2662
|
+
* its components, so its targets, functions, and surfaces stay current. The surfaces are those of the running thread.
|
|
2663
|
+
*
|
|
2664
|
+
* @param copilotkit - The CopilotKit core of the provider.
|
|
2665
|
+
* @param runtime - The generative UI runtime parts, or null without one.
|
|
2666
|
+
* @param options - `enabled` and `agentIds`.
|
|
2667
|
+
*
|
|
2668
|
+
* @example
|
|
2669
|
+
* ```ts
|
|
2670
|
+
* useGenerativeUIMarkdownContext(copilotkit, core, {
|
|
2671
|
+
* enabled: config?.transport === "markdown" && config.sendContext !== false,
|
|
2672
|
+
* agentIds: config?.agentIds,
|
|
2673
|
+
* });
|
|
2674
|
+
* ```
|
|
2675
|
+
*/
|
|
2676
|
+
function useGenerativeUIMarkdownContext(copilotkit, runtime, options) {
|
|
2677
|
+
const { enabled, agentIds } = options;
|
|
2678
|
+
const agentIdsKey = agentIds === void 0 ? "" : JSON.stringify(agentIds);
|
|
2679
|
+
(0, react.useEffect)(() => {
|
|
2680
|
+
if (!runtime || !enabled) return;
|
|
2681
|
+
const scopedAgentIds = agentIdsKey ? JSON.parse(agentIdsKey) : void 0;
|
|
2682
|
+
const scope = scopedAgentIds ? { agentIds: scopedAgentIds } : {};
|
|
2683
|
+
let guidelineId;
|
|
2684
|
+
let guidelineValue;
|
|
2685
|
+
let schemaId;
|
|
2686
|
+
let schemaValue;
|
|
2687
|
+
const refreshSchema = (threadId) => {
|
|
2688
|
+
const manifest = buildGenerativeUIManifest(runtime, threadId);
|
|
2689
|
+
const guideline = (0, _copilotkit_shared.buildGenerativeUIMarkdownGuidelines)((0, _copilotkit_shared.generativeUIGuideFeatures)(manifest));
|
|
2690
|
+
if (guideline !== guidelineValue) {
|
|
2691
|
+
if (guidelineId !== void 0) copilotkit.removeContext(guidelineId);
|
|
2692
|
+
guidelineValue = guideline;
|
|
2693
|
+
guidelineId = copilotkit.addContext({
|
|
2694
|
+
description: _copilotkit_shared.GENERATIVE_UI_MARKDOWN_GUIDELINES_DESCRIPTION,
|
|
2695
|
+
value: guideline,
|
|
2696
|
+
...scope
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
const value = JSON.stringify(buildCompactGenerativeUISchema(manifest));
|
|
2700
|
+
if (value === schemaValue) return;
|
|
2701
|
+
if (schemaId !== void 0) copilotkit.removeContext(schemaId);
|
|
2702
|
+
schemaValue = value;
|
|
2703
|
+
schemaId = copilotkit.addContext({
|
|
2704
|
+
description: _copilotkit_shared.GENERATIVE_UI_MARKDOWN_SCHEMA_CONTEXT_DESCRIPTION,
|
|
2705
|
+
value,
|
|
2706
|
+
...scope
|
|
2707
|
+
});
|
|
2708
|
+
};
|
|
2709
|
+
refreshSchema(runtime.manager.getActiveThread());
|
|
2710
|
+
let targetsKey = JSON.stringify(runtime.manager.getMountedTargets());
|
|
2711
|
+
const unsubscribeManager = runtime.manager.subscribe(() => {
|
|
2712
|
+
const nextKey = JSON.stringify(runtime.manager.getMountedTargets());
|
|
2713
|
+
if (nextKey === targetsKey) return;
|
|
2714
|
+
targetsKey = nextKey;
|
|
2715
|
+
refreshSchema(runtime.manager.getActiveThread());
|
|
2716
|
+
});
|
|
2717
|
+
const subscription = copilotkit.subscribe({ onAgentRunStarted: ({ agent }) => refreshSchema(agent.threadId) });
|
|
2718
|
+
return () => {
|
|
2719
|
+
subscription.unsubscribe();
|
|
2720
|
+
unsubscribeManager();
|
|
2721
|
+
if (guidelineId !== void 0) copilotkit.removeContext(guidelineId);
|
|
2722
|
+
if (schemaId !== void 0) copilotkit.removeContext(schemaId);
|
|
2723
|
+
};
|
|
2724
|
+
}, [
|
|
2725
|
+
copilotkit,
|
|
2726
|
+
runtime,
|
|
2727
|
+
enabled,
|
|
2728
|
+
agentIdsKey
|
|
2729
|
+
]);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
//#endregion
|
|
2733
|
+
//#region src/v2/generative-ui/debug-source.ts
|
|
2734
|
+
/**
|
|
2735
|
+
* What a run of `threadId` sends: its envelope, and the text that goes into
|
|
2736
|
+
* the prompt of the model because of it.
|
|
2737
|
+
*
|
|
2738
|
+
* - Tool transport: the runtime adds the `render_ui` guide. It builds it
|
|
2739
|
+
* from the envelope with `buildGenerativeUIPrompt`, as done here.
|
|
2740
|
+
* - Markdown transport: the provider sends the guideline and the compact
|
|
2741
|
+
* schema as context entries. With `sendContext: false`, the backend builds
|
|
2742
|
+
* its own prompt, usually from the `instructions` of the envelope.
|
|
2743
|
+
*/
|
|
2744
|
+
function describeGenerativeUIRun(runtime, threadId, options) {
|
|
2745
|
+
const envelope = buildGenerativeUIEnvelope(runtime, threadId, options);
|
|
2746
|
+
if (options.transport === "tool") return {
|
|
2747
|
+
envelope,
|
|
2748
|
+
prompt: [{
|
|
2749
|
+
title: "render_ui guide",
|
|
2750
|
+
source: "runtime",
|
|
2751
|
+
text: (0, _copilotkit_shared.buildGenerativeUIPrompt)((0, _copilotkit_shared.envelopeToManifest)(envelope))
|
|
2752
|
+
}]
|
|
2753
|
+
};
|
|
2754
|
+
const manifest = buildGenerativeUIManifest(runtime, threadId);
|
|
2755
|
+
if (!options.contextSent) return {
|
|
2756
|
+
envelope,
|
|
2757
|
+
prompt: [{
|
|
2758
|
+
title: "Envelope instructions",
|
|
2759
|
+
source: "backend",
|
|
2760
|
+
text: (0, _copilotkit_shared.envelopeToManifest)(envelope).instructions ?? ""
|
|
2761
|
+
}]
|
|
2762
|
+
};
|
|
2763
|
+
const guideline = (0, _copilotkit_shared.buildGenerativeUIMarkdownGuidelines)((0, _copilotkit_shared.generativeUIGuideFeatures)(manifest));
|
|
2764
|
+
const schema = JSON.stringify(buildCompactGenerativeUISchema(manifest));
|
|
2765
|
+
return {
|
|
2766
|
+
envelope,
|
|
2767
|
+
prompt: [{
|
|
2768
|
+
title: "Markdown guideline",
|
|
2769
|
+
source: "context",
|
|
2770
|
+
text: guideline
|
|
2771
|
+
}, {
|
|
2772
|
+
title: "Component schema",
|
|
2773
|
+
source: "context",
|
|
2774
|
+
text: schema
|
|
2775
|
+
}]
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
/** The declared functions, with the JSON Schema of their parameters. */
|
|
2779
|
+
function debugFunctions(runtime) {
|
|
2780
|
+
const functions = {};
|
|
2781
|
+
for (const [name, declared] of runtime.ui.functions) {
|
|
2782
|
+
let parameters = {};
|
|
2783
|
+
try {
|
|
2784
|
+
parameters = toJsonSchema(declared.parameters);
|
|
2785
|
+
} catch {}
|
|
2786
|
+
functions[name] = {
|
|
2787
|
+
...declared.description ? { description: declared.description } : {},
|
|
2788
|
+
parameters,
|
|
2789
|
+
handlers: runtime.dispatcher.handlerCount(name)
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
return functions;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* The debug source that the provider gives core for the inspector. It builds
|
|
2796
|
+
* a snapshot only when the inspector reads one.
|
|
2797
|
+
*
|
|
2798
|
+
* @param runtime - The generative UI parts of the provider.
|
|
2799
|
+
* @param getOptions - The envelope options and agent ids of the current config.
|
|
2800
|
+
* @returns The source, and `recordRun` for each run start.
|
|
2801
|
+
*/
|
|
2802
|
+
function createGenerativeUIDebugSource(runtime, getOptions) {
|
|
2803
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2804
|
+
let lastRun;
|
|
2805
|
+
const notify = () => {
|
|
2806
|
+
for (const listener of [...listeners]) listener();
|
|
2807
|
+
};
|
|
2808
|
+
return {
|
|
2809
|
+
source: {
|
|
2810
|
+
getSnapshot() {
|
|
2811
|
+
const { options, agentIds } = getOptions();
|
|
2812
|
+
const threadId = runtime.manager.getActiveThread();
|
|
2813
|
+
const manifest = buildGenerativeUIManifest(runtime, threadId);
|
|
2814
|
+
return {
|
|
2815
|
+
catalogId: runtime.ui.id,
|
|
2816
|
+
transport: options.transport,
|
|
2817
|
+
...agentIds ? { agentIds } : {},
|
|
2818
|
+
components: runtime.ui.manifestComponents,
|
|
2819
|
+
componentCatalogs: Object.fromEntries(componentCatalogIds(runtime.ui)),
|
|
2820
|
+
functions: debugFunctions(runtime),
|
|
2821
|
+
state: { ...runtime.manager.getState() },
|
|
2822
|
+
targets: manifest.targets,
|
|
2823
|
+
activeThreadId: threadId,
|
|
2824
|
+
surfaces: runtime.manager.debugSurfaces(),
|
|
2825
|
+
lastRun,
|
|
2826
|
+
nextRun: {
|
|
2827
|
+
agentId: void 0,
|
|
2828
|
+
threadId,
|
|
2829
|
+
...describeGenerativeUIRun(runtime, threadId, options)
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
},
|
|
2833
|
+
subscribe(listener) {
|
|
2834
|
+
listeners.add(listener);
|
|
2835
|
+
const unsubscribe = runtime.manager.subscribe(listener);
|
|
2836
|
+
return () => {
|
|
2837
|
+
listeners.delete(listener);
|
|
2838
|
+
unsubscribe();
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
recordRun(run) {
|
|
2843
|
+
lastRun = run;
|
|
2844
|
+
notify();
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
//#endregion
|
|
2850
|
+
//#region src/v2/generative-ui/errors.ts
|
|
2851
|
+
var GenerativeUIError = class extends Error {
|
|
2852
|
+
constructor(code, message) {
|
|
2853
|
+
super(message);
|
|
2854
|
+
this.name = "GenerativeUIError";
|
|
2855
|
+
this.code = code;
|
|
2856
|
+
}
|
|
2857
|
+
};
|
|
2858
|
+
|
|
2859
|
+
//#endregion
|
|
2860
|
+
//#region src/v2/generative-ui/functions.ts
|
|
2861
|
+
/**
|
|
2862
|
+
* The renderer-side functions of the A2UI 1.0 basic catalog. Each one takes
|
|
2863
|
+
* resolved args (no bindings left in them) and gives a plain value. They
|
|
2864
|
+
* run synchronously, so a prop bound to one updates in the same render as
|
|
2865
|
+
* the data it reads. The signatures the model sees are in
|
|
2866
|
+
* `@copilotkit/shared` (`A2UI_BASIC_FUNCTIONS`).
|
|
2867
|
+
*/
|
|
2868
|
+
/** Thrown for a bad call: an unknown function, bad args, or a bad value. */
|
|
2869
|
+
var ExpressionError = class extends Error {
|
|
2870
|
+
constructor(message) {
|
|
2871
|
+
super(message);
|
|
2872
|
+
this.name = "ExpressionError";
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
/** The boolean of a DynamicBoolean: a ValidationResult counts as `valid`. */
|
|
2876
|
+
function truthOf(value) {
|
|
2877
|
+
if ((0, _copilotkit_shared.isValidationResult)(value)) return value.valid;
|
|
2878
|
+
return Boolean(value);
|
|
2879
|
+
}
|
|
2880
|
+
const valid = () => ({ valid: true });
|
|
2881
|
+
/** A failed result. `code` names the failure, the same for every locale. */
|
|
2882
|
+
const invalid = (message, code) => ({
|
|
2883
|
+
valid: false,
|
|
2884
|
+
message,
|
|
2885
|
+
code
|
|
2886
|
+
});
|
|
2887
|
+
function text(value) {
|
|
2888
|
+
if (value === void 0 || value === null) return "";
|
|
2889
|
+
return typeof value === "string" ? value : String(value);
|
|
2890
|
+
}
|
|
2891
|
+
function numberOf$1(value) {
|
|
2892
|
+
if (typeof value === "number") return value;
|
|
2893
|
+
if (typeof value === "string" && value.trim() !== "") return Number(value);
|
|
2894
|
+
return NaN;
|
|
2895
|
+
}
|
|
2896
|
+
function optionalNumber(value, name) {
|
|
2897
|
+
if (value === void 0 || value === null) return void 0;
|
|
2898
|
+
const parsed = numberOf$1(value);
|
|
2899
|
+
if (Number.isNaN(parsed)) throw new ExpressionError(`${name} must be a number`);
|
|
2900
|
+
return parsed;
|
|
2901
|
+
}
|
|
2902
|
+
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2903
|
+
/** The locale of the user. `undefined` lets Intl pick the default. */
|
|
2904
|
+
function locale() {
|
|
2905
|
+
return typeof navigator !== "undefined" ? navigator.language : void 0;
|
|
2906
|
+
}
|
|
2907
|
+
function numberFormat(args, options) {
|
|
2908
|
+
const decimals = optionalNumber(args.decimals, "decimals");
|
|
2909
|
+
return new Intl.NumberFormat(locale(), {
|
|
2910
|
+
...options,
|
|
2911
|
+
useGrouping: args.grouping !== false,
|
|
2912
|
+
...decimals === void 0 ? {} : {
|
|
2913
|
+
minimumFractionDigits: decimals,
|
|
2914
|
+
maximumFractionDigits: decimals
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
/** A date from an ISO string, a timestamp, or a Date. */
|
|
2919
|
+
function dateOf(value) {
|
|
2920
|
+
const date = value instanceof Date ? value : typeof value === "number" ? new Date(value) : typeof value === "string" ? new Date(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00` : value) : void 0;
|
|
2921
|
+
if (date === void 0 || Number.isNaN(date.getTime())) throw new ExpressionError("value must be a date");
|
|
2922
|
+
return date;
|
|
2923
|
+
}
|
|
2924
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
2925
|
+
/**
|
|
2926
|
+
* A TR35 field is a run of one letter ("EEE" is one abbreviated weekday,
|
|
2927
|
+
* not three), and text in single quotes is literal. Letters that are not
|
|
2928
|
+
* fields stay as they are.
|
|
2929
|
+
*/
|
|
2930
|
+
const DATE_FIELD = /([A-Za-z])\1*|'(?:[^']|'')*'/g;
|
|
2931
|
+
function formatDatePattern(date, pattern) {
|
|
2932
|
+
const parts = (options) => new Intl.DateTimeFormat(locale(), options).format(date);
|
|
2933
|
+
const hours12 = date.getHours() % 12 || 12;
|
|
2934
|
+
const numeric = (value, width) => width >= 2 ? pad(value, 2) : String(value);
|
|
2935
|
+
return pattern.replace(DATE_FIELD, (token) => {
|
|
2936
|
+
if (token.startsWith("'")) return token.slice(1, -1).replace(/''/g, "'");
|
|
2937
|
+
const width = token.length;
|
|
2938
|
+
switch (token[0]) {
|
|
2939
|
+
case "y": return width === 2 ? pad(date.getFullYear() % 100, 2) : String(date.getFullYear());
|
|
2940
|
+
case "M":
|
|
2941
|
+
if (width >= 4) return parts({ month: "long" });
|
|
2942
|
+
if (width === 3) return parts({ month: "short" });
|
|
2943
|
+
return numeric(date.getMonth() + 1, width);
|
|
2944
|
+
case "d": return numeric(date.getDate(), width);
|
|
2945
|
+
case "E": return parts({ weekday: width >= 4 ? "long" : "short" });
|
|
2946
|
+
case "H": return numeric(date.getHours(), width);
|
|
2947
|
+
case "h": return numeric(hours12, width);
|
|
2948
|
+
case "m": return pad(date.getMinutes(), 2);
|
|
2949
|
+
case "s": return pad(date.getSeconds(), 2);
|
|
2950
|
+
case "a": return date.getHours() < 12 ? "AM" : "PM";
|
|
2951
|
+
default: return token;
|
|
2952
|
+
}
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
/** Opens an http(s) URL in a new tab. Any other scheme is refused. */
|
|
2956
|
+
function openUrlSafely(url) {
|
|
2957
|
+
if (typeof url !== "string" || url === "") throw new ExpressionError("url must be a string");
|
|
2958
|
+
if (typeof window === "undefined") return;
|
|
2959
|
+
let parsed;
|
|
2960
|
+
try {
|
|
2961
|
+
parsed = new URL(url, window.location.href);
|
|
2962
|
+
} catch {
|
|
2963
|
+
throw new ExpressionError(`"${url}" is not a URL`);
|
|
2964
|
+
}
|
|
2965
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new ExpressionError(`openUrl takes only http(s) URLs, not ${parsed.protocol}`);
|
|
2966
|
+
window.open(parsed.href, "_blank", "noopener,noreferrer");
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* The basic functions, except `formatString`, which needs the resolver.
|
|
2970
|
+
* `calc` and `series` are added by `resolve.ts`, which imports both.
|
|
2971
|
+
*/
|
|
2972
|
+
const BASIC_FUNCTIONS = {
|
|
2973
|
+
required: ({ value }) => {
|
|
2974
|
+
return value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0 ? invalid("Required", "required") : valid();
|
|
2975
|
+
},
|
|
2976
|
+
regex: ({ value, pattern }) => {
|
|
2977
|
+
if (typeof pattern !== "string") throw new ExpressionError("pattern must be a string");
|
|
2978
|
+
let expression;
|
|
2979
|
+
try {
|
|
2980
|
+
expression = new RegExp(pattern, "u");
|
|
2981
|
+
} catch {
|
|
2982
|
+
throw new ExpressionError(`"${pattern}" is not a regular expression`);
|
|
2983
|
+
}
|
|
2984
|
+
return expression.test(text(value)) ? valid() : invalid("Invalid format", "pattern");
|
|
2985
|
+
},
|
|
2986
|
+
length: ({ value, min, max }) => {
|
|
2987
|
+
const size = text(value).length;
|
|
2988
|
+
const low = optionalNumber(min, "min");
|
|
2989
|
+
const high = optionalNumber(max, "max");
|
|
2990
|
+
if (low !== void 0 && size < low) return invalid(`At least ${low} characters`, "tooShort");
|
|
2991
|
+
if (high !== void 0 && size > high) return invalid(`At most ${high} characters`, "tooLong");
|
|
2992
|
+
return valid();
|
|
2993
|
+
},
|
|
2994
|
+
numeric: ({ value, min, max }) => {
|
|
2995
|
+
const parsed = numberOf$1(value);
|
|
2996
|
+
if (Number.isNaN(parsed)) return invalid("Must be a number", "notANumber");
|
|
2997
|
+
const low = optionalNumber(min, "min");
|
|
2998
|
+
const high = optionalNumber(max, "max");
|
|
2999
|
+
if (low !== void 0 && parsed < low) return invalid(`At least ${low}`, "tooSmall");
|
|
3000
|
+
if (high !== void 0 && parsed > high) return invalid(`At most ${high}`, "tooLarge");
|
|
3001
|
+
return valid();
|
|
3002
|
+
},
|
|
3003
|
+
email: ({ value }) => EMAIL.test(text(value)) ? valid() : invalid("Invalid email address", "email"),
|
|
3004
|
+
formatNumber: (args) => {
|
|
3005
|
+
const parsed = numberOf$1(args.value);
|
|
3006
|
+
if (Number.isNaN(parsed)) throw new ExpressionError("value must be a number");
|
|
3007
|
+
return numberFormat(args, {}).format(parsed);
|
|
3008
|
+
},
|
|
3009
|
+
formatCurrency: (args) => {
|
|
3010
|
+
const parsed = numberOf$1(args.value);
|
|
3011
|
+
if (Number.isNaN(parsed)) throw new ExpressionError("value must be a number");
|
|
3012
|
+
if (typeof args.currency !== "string" || args.currency === "") throw new ExpressionError("currency must be an ISO 4217 code");
|
|
3013
|
+
try {
|
|
3014
|
+
return numberFormat(args, {
|
|
3015
|
+
style: "currency",
|
|
3016
|
+
currency: args.currency
|
|
3017
|
+
}).format(parsed);
|
|
3018
|
+
} catch {
|
|
3019
|
+
throw new ExpressionError(`"${args.currency}" is not a currency code`);
|
|
3020
|
+
}
|
|
3021
|
+
},
|
|
3022
|
+
formatDate: ({ value, format }) => {
|
|
3023
|
+
if (typeof format !== "string") throw new ExpressionError("format must be a string");
|
|
3024
|
+
return formatDatePattern(dateOf(value), format);
|
|
3025
|
+
},
|
|
3026
|
+
pluralize: (args) => {
|
|
3027
|
+
const parsed = numberOf$1(args.value);
|
|
3028
|
+
if (Number.isNaN(parsed)) throw new ExpressionError("value must be a number");
|
|
3029
|
+
const category = new Intl.PluralRules(locale()).select(parsed);
|
|
3030
|
+
return text((parsed === 0 ? args.zero : void 0) ?? args[category] ?? args.other);
|
|
3031
|
+
},
|
|
3032
|
+
and: ({ values }) => {
|
|
3033
|
+
if (!Array.isArray(values)) throw new ExpressionError("values must be a list");
|
|
3034
|
+
return values.every(truthOf);
|
|
3035
|
+
},
|
|
3036
|
+
or: ({ values }) => {
|
|
3037
|
+
if (!Array.isArray(values)) throw new ExpressionError("values must be a list");
|
|
3038
|
+
return values.some(truthOf);
|
|
3039
|
+
},
|
|
3040
|
+
not: ({ value }) => !truthOf(value),
|
|
3041
|
+
openUrl: ({ url }) => {
|
|
3042
|
+
openUrlSafely(url);
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
/** True when the basic function `name` must run from a user gesture. */
|
|
3046
|
+
function requiresUserActivation(name) {
|
|
3047
|
+
return _copilotkit_shared.GENERATIVE_UI_FUNCTIONS[name]?.requiresUserActivation === true;
|
|
3048
|
+
}
|
|
3049
|
+
/** True when a basic function gives a ValidationResult. */
|
|
3050
|
+
function givesValidationResult(name) {
|
|
3051
|
+
return _copilotkit_shared.GENERATIVE_UI_FUNCTIONS[name]?.returnType === "validationResult";
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
//#endregion
|
|
3055
|
+
//#region src/v2/generative-ui/dispatcher.ts
|
|
3056
|
+
const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
3057
|
+
/**
|
|
3058
|
+
* Creates the registry of functions that generated UI may call.
|
|
3059
|
+
*
|
|
3060
|
+
* A function is callable when it is declared in `getDeclared()` and exactly
|
|
3061
|
+
* one handler is mounted for it with `register`. Names are resolved at call
|
|
3062
|
+
* time and fail closed. Nothing is sent to the agent.
|
|
3063
|
+
* Neither `invoke` nor `listExposed` throws.
|
|
3064
|
+
*
|
|
3065
|
+
* @param options.getDeclared - Returns the declared functions.
|
|
3066
|
+
* @param options.onError - Gets each failed call.
|
|
3067
|
+
*
|
|
3068
|
+
* @example
|
|
3069
|
+
* ```ts
|
|
3070
|
+
* const dispatcher = createGenerativeUIDispatcher({
|
|
3071
|
+
* getDeclared: () => new Map(Object.entries(ui.functions)),
|
|
3072
|
+
* });
|
|
3073
|
+
* const unregister = dispatcher.register("addToCart", handlerRef);
|
|
3074
|
+
* await dispatcher.invoke("addToCart", { productId: "p1" }, origin);
|
|
3075
|
+
* unregister();
|
|
3076
|
+
* ```
|
|
3077
|
+
*/
|
|
3078
|
+
/**
|
|
3079
|
+
* True while the browser counts the current task as user-activated (a
|
|
3080
|
+
* click, a key press). Where the API is missing (older browsers, test
|
|
3081
|
+
* environments), the gesture is assumed.
|
|
3082
|
+
*/
|
|
3083
|
+
function hasUserActivation() {
|
|
3084
|
+
if (typeof navigator === "undefined") return true;
|
|
3085
|
+
const activation = navigator.userActivation;
|
|
3086
|
+
return activation === void 0 || activation.isActive;
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* The permissions of a declared function as the manifest states them: the
|
|
3090
|
+
* callers always written out, so a reader needs no default.
|
|
3091
|
+
*/
|
|
3092
|
+
function permissionsOf(fn) {
|
|
3093
|
+
const callers = (0, _copilotkit_shared.functionCallers)(fn);
|
|
3094
|
+
return {
|
|
3095
|
+
allowedCallers: callers.renderer && callers.agent ? "rendererOrAgent" : callers.agent ? "agentOnly" : "rendererOnly",
|
|
3096
|
+
...fn.requiresUserActivation ? { requiresUserActivation: true } : {}
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
function createGenerativeUIDispatcher(options) {
|
|
3100
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
3101
|
+
const resolveUnchecked = (name) => {
|
|
3102
|
+
const declared = options.getDeclared().get(name);
|
|
3103
|
+
if (!declared) return {
|
|
3104
|
+
ok: false,
|
|
3105
|
+
error: new GenerativeUIError("function_not_found", `No generative UI function named "${name}" is declared in createGenerativeUI({ functions })`)
|
|
3106
|
+
};
|
|
3107
|
+
const mounted = [...handlers.get(name) ?? []];
|
|
3108
|
+
const [ref] = mounted;
|
|
3109
|
+
if (!ref) return {
|
|
3110
|
+
ok: false,
|
|
3111
|
+
error: new GenerativeUIError("function_not_found", `"${name}" is declared, but no useGenerativeUIFunction handler is mounted for it`)
|
|
3112
|
+
};
|
|
3113
|
+
if (mounted.length > 1) return {
|
|
3114
|
+
ok: false,
|
|
3115
|
+
error: new GenerativeUIError("function_conflict", `"${name}" has ${mounted.length} useGenerativeUIFunction handlers mounted. Mount exactly one.`)
|
|
3116
|
+
};
|
|
3117
|
+
return {
|
|
3118
|
+
ok: true,
|
|
3119
|
+
declared,
|
|
3120
|
+
ref
|
|
3121
|
+
};
|
|
3122
|
+
};
|
|
3123
|
+
const isDeclared = (name) => {
|
|
3124
|
+
try {
|
|
3125
|
+
return options.getDeclared().has(name);
|
|
3126
|
+
} catch {
|
|
3127
|
+
return false;
|
|
3128
|
+
}
|
|
3129
|
+
};
|
|
3130
|
+
const resolve = (name) => {
|
|
3131
|
+
try {
|
|
3132
|
+
return resolveUnchecked(name);
|
|
3133
|
+
} catch (error) {
|
|
3134
|
+
return {
|
|
3135
|
+
ok: false,
|
|
3136
|
+
error: new GenerativeUIError("handler_failed", `"${name}" could not be resolved: ${messageOf(error)}`)
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
};
|
|
3140
|
+
const validateArgs = async (name, schema, args) => {
|
|
3141
|
+
try {
|
|
3142
|
+
const validation = await schema["~standard"].validate(args);
|
|
3143
|
+
if (!validation.issues) return {
|
|
3144
|
+
ok: true,
|
|
3145
|
+
value: validation.value
|
|
3146
|
+
};
|
|
3147
|
+
return {
|
|
3148
|
+
ok: false,
|
|
3149
|
+
error: new GenerativeUIError("invalid_arguments", `Invalid arguments for "${name}": ${validation.issues.map((i) => `${(i.path ?? []).map((p) => typeof p === "object" ? p.key : p).join(".") || "args"}: ${i.message}`).join("; ")}`)
|
|
3150
|
+
};
|
|
3151
|
+
} catch (error) {
|
|
3152
|
+
return {
|
|
3153
|
+
ok: false,
|
|
3154
|
+
error: new GenerativeUIError("invalid_arguments", `Invalid arguments for "${name}": ${messageOf(error)}`)
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
const failWith = (name, origin, error) => {
|
|
3159
|
+
try {
|
|
3160
|
+
options.onError?.({
|
|
3161
|
+
name,
|
|
3162
|
+
origin,
|
|
3163
|
+
error
|
|
3164
|
+
});
|
|
3165
|
+
} catch {}
|
|
3166
|
+
return {
|
|
3167
|
+
ok: false,
|
|
3168
|
+
error
|
|
3169
|
+
};
|
|
3170
|
+
};
|
|
3171
|
+
/**
|
|
3172
|
+
* Why `origin` may not call a function with these permissions now, as an
|
|
3173
|
+
* error, or `undefined` when it may. The agent is refused what is for the
|
|
3174
|
+
* renderer; a component is refused what is for the agent, and a gesture
|
|
3175
|
+
* function when the browser counts no user activation.
|
|
3176
|
+
*/
|
|
3177
|
+
const callerError = (name, fn, origin) => {
|
|
3178
|
+
const isComponent = "componentId" in origin;
|
|
3179
|
+
if (!isComponent && !_copilotkit_shared.GENERATIVE_UI_AGENT_FUNCTION_CALLS) return new GenerativeUIError("invalid_function_call", _copilotkit_shared.GENERATIVE_UI_AGENT_FUNCTION_CALLS_OFF_REASON);
|
|
3180
|
+
const problem = (0, _copilotkit_shared.callerProblem)(name, fn, isComponent ? "renderer" : "agent");
|
|
3181
|
+
if (problem !== void 0) return new GenerativeUIError("invalid_function_call", problem);
|
|
3182
|
+
if (isComponent && fn.requiresUserActivation && !hasUserActivation()) return new GenerativeUIError("invalid_function_call", `function "${name}" runs only from a user gesture; this event was not one`);
|
|
3183
|
+
};
|
|
3184
|
+
/**
|
|
3185
|
+
* Runs a basic function of the A2UI catalog. They are value functions of
|
|
3186
|
+
* the renderer: the agent cannot call them, and one that needs a user
|
|
3187
|
+
* gesture runs only from an event binding.
|
|
3188
|
+
*/
|
|
3189
|
+
const invokeBasic = (name, args, origin) => {
|
|
3190
|
+
const refused = callerError(name, _copilotkit_shared.GENERATIVE_UI_FUNCTIONS[name], origin);
|
|
3191
|
+
if (refused) return failWith(name, origin, refused);
|
|
3192
|
+
if (args !== void 0 && !(typeof args === "object" && args !== null && !Array.isArray(args))) return failWith(name, origin, new GenerativeUIError("invalid_arguments", `Invalid arguments for "${name}": args must be an object`));
|
|
3193
|
+
try {
|
|
3194
|
+
return {
|
|
3195
|
+
ok: true,
|
|
3196
|
+
result: BASIC_FUNCTIONS[name](args ?? {})
|
|
3197
|
+
};
|
|
3198
|
+
} catch (error) {
|
|
3199
|
+
return failWith(name, origin, new GenerativeUIError(error instanceof ExpressionError ? "invalid_arguments" : "handler_failed", `"${name}" failed: ${messageOf(error)}`));
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
return {
|
|
3203
|
+
async invoke(name, args, origin) {
|
|
3204
|
+
const resolved = resolve(name);
|
|
3205
|
+
if (!resolved.ok && resolved.error.code === "function_not_found" && !isDeclared(name) && Object.prototype.hasOwnProperty.call(BASIC_FUNCTIONS, name)) return invokeBasic(name, args, origin);
|
|
3206
|
+
if (!resolved.ok) return failWith(name, origin, resolved.error);
|
|
3207
|
+
const refused = callerError(name, resolved.declared, origin);
|
|
3208
|
+
if (refused) return failWith(name, origin, refused);
|
|
3209
|
+
const validated = await validateArgs(name, resolved.declared.parameters, args ?? {});
|
|
3210
|
+
if (!validated.ok) return failWith(name, origin, validated.error);
|
|
3211
|
+
try {
|
|
3212
|
+
return {
|
|
3213
|
+
ok: true,
|
|
3214
|
+
result: await resolved.ref.current(validated.value)
|
|
3215
|
+
};
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
return failWith(name, origin, new GenerativeUIError("handler_failed", `"${name}" failed: ${messageOf(error)}`));
|
|
3218
|
+
}
|
|
3219
|
+
},
|
|
3220
|
+
listExposed() {
|
|
3221
|
+
let names;
|
|
3222
|
+
try {
|
|
3223
|
+
names = [...options.getDeclared().keys()];
|
|
3224
|
+
} catch {
|
|
3225
|
+
return {};
|
|
3226
|
+
}
|
|
3227
|
+
const listed = {};
|
|
3228
|
+
for (const name of names) {
|
|
3229
|
+
const resolved = resolve(name);
|
|
3230
|
+
if (!resolved.ok) continue;
|
|
3231
|
+
if (!(0, _copilotkit_shared.functionCallers)(resolved.declared).renderer && !_copilotkit_shared.GENERATIVE_UI_AGENT_FUNCTION_CALLS) continue;
|
|
3232
|
+
const { description, parameters, returnType } = resolved.declared;
|
|
3233
|
+
try {
|
|
3234
|
+
listed[name] = {
|
|
3235
|
+
...description ? { description } : {},
|
|
3236
|
+
parameters: toJsonSchema(parameters),
|
|
3237
|
+
...returnType ? { returnType } : {},
|
|
3238
|
+
...permissionsOf(resolved.declared)
|
|
3239
|
+
};
|
|
3240
|
+
} catch {}
|
|
3241
|
+
}
|
|
3242
|
+
return listed;
|
|
3243
|
+
},
|
|
3244
|
+
register(name, ref) {
|
|
3245
|
+
const mounted = handlers.get(name) ?? /* @__PURE__ */ new Set();
|
|
3246
|
+
mounted.add(ref);
|
|
3247
|
+
handlers.set(name, mounted);
|
|
3248
|
+
return () => {
|
|
3249
|
+
mounted.delete(ref);
|
|
3250
|
+
};
|
|
3251
|
+
},
|
|
3252
|
+
handlerCount: (name) => handlers.get(name)?.size ?? 0
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
//#endregion
|
|
3257
|
+
//#region src/v2/generative-ui/markdown/source.ts
|
|
3258
|
+
/**
|
|
3259
|
+
* Values by key, from the latest build. A value that a build does not use
|
|
3260
|
+
* is dropped at the end of that build, so the cache does not grow.
|
|
3261
|
+
*/
|
|
3262
|
+
var BuildMemo = class {
|
|
3263
|
+
constructor() {
|
|
3264
|
+
this.previous = /* @__PURE__ */ new Map();
|
|
3265
|
+
this.current = /* @__PURE__ */ new Map();
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* The value of `key`. It is computed when no earlier value is there, or
|
|
3269
|
+
* when `isFresh` says that the earlier value is out of date.
|
|
3270
|
+
*/
|
|
3271
|
+
get(key, compute, isFresh = () => true) {
|
|
3272
|
+
let value = this.current.get(key) ?? this.previous.get(key);
|
|
3273
|
+
if (value === void 0 || !isFresh(value)) value = compute();
|
|
3274
|
+
this.current.set(key, value);
|
|
3275
|
+
return value;
|
|
3276
|
+
}
|
|
3277
|
+
endBuild() {
|
|
3278
|
+
this.previous = this.current;
|
|
3279
|
+
this.current = /* @__PURE__ */ new Map();
|
|
3280
|
+
}
|
|
3281
|
+
};
|
|
3282
|
+
/** The results of the builds of one thread. See `BuildMarkdownSurfacesOptions.cache`. */
|
|
3283
|
+
var MarkdownBuildCache = class {
|
|
3284
|
+
constructor() {
|
|
3285
|
+
this.segments = new BuildMemo();
|
|
3286
|
+
this.fences = new BuildMemo();
|
|
3287
|
+
this.drafts = new BuildMemo();
|
|
3288
|
+
this.readdressed = /* @__PURE__ */ new WeakMap();
|
|
3289
|
+
}
|
|
3290
|
+
endBuild() {
|
|
3291
|
+
this.segments.endBuild();
|
|
3292
|
+
this.fences.endBuild();
|
|
3293
|
+
this.drafts.endBuild();
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3296
|
+
/** True when both lists hold the same objects in the same order. */
|
|
3297
|
+
function sameItems(a, b) {
|
|
3298
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
3299
|
+
}
|
|
3300
|
+
function isString(value) {
|
|
3301
|
+
return typeof value === "string";
|
|
3302
|
+
}
|
|
3303
|
+
/** The key of one fence of one message. */
|
|
3304
|
+
function fenceKey(messageId, fenceIndex) {
|
|
3305
|
+
return `${messageId}:${fenceIndex}`;
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* The operations of a fence body. While the fence streams, the last object
|
|
3309
|
+
* can be unfinished. Of an unfinished updateComponents or createSurface, the
|
|
3310
|
+
* components that are complete render at once (a createSurface also keeps
|
|
3311
|
+
* its data model when all of it arrived). Any other unfinished object is
|
|
3312
|
+
* left out, because a part of a data model value can still change. A final
|
|
3313
|
+
* fence keeps what can be saved from an unfinished object.
|
|
3314
|
+
*/
|
|
3315
|
+
function fenceOperations(text, isStreaming) {
|
|
3316
|
+
const parsed = (0, _copilotkit_core.extractFenceOperations)(text);
|
|
3317
|
+
if (!isStreaming || parsed.partialObject === null) return parsed.operations;
|
|
3318
|
+
const complete = (0, _copilotkit_core.extractFenceOperations)(text.slice(0, text.length - parsed.partialObject.length)).operations;
|
|
3319
|
+
const partial = parsed.operations.length > complete.length ? parsed.operations[parsed.operations.length - 1] : void 0;
|
|
3320
|
+
return partial !== void 0 && (isRecord(partial.updateComponents) || isRecord(partial.createSurface)) ? [...complete, partial] : complete;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* An inline createSurface becomes a createSurface with only its surfaceId
|
|
3324
|
+
* and catalog, then an updateDataModel for its data model, then an
|
|
3325
|
+
* updateComponents for its components. The prose lift and the split per
|
|
3326
|
+
* component read only updateComponents, so they then work for an inline
|
|
3327
|
+
* surface too. A createSurface with a data model or components of a wrong
|
|
3328
|
+
* shape does not change, so that validation rejects it with the reason.
|
|
3329
|
+
*/
|
|
3330
|
+
function expandInlineCreate(operations) {
|
|
3331
|
+
return operations.flatMap((op) => {
|
|
3332
|
+
const create = op.createSurface;
|
|
3333
|
+
if (!isRecord(create)) return [op];
|
|
3334
|
+
const { dataModel, components, ...rest } = create;
|
|
3335
|
+
const hasData = isRecord(dataModel);
|
|
3336
|
+
const hasComponents = Array.isArray(components) && components.length > 0;
|
|
3337
|
+
if (!((dataModel === void 0 || hasData) && (components === void 0 || hasComponents)) || !hasData && !hasComponents) return [op];
|
|
3338
|
+
const { surfaceId } = create;
|
|
3339
|
+
const expanded = [{
|
|
3340
|
+
...op,
|
|
3341
|
+
createSurface: rest
|
|
3342
|
+
}];
|
|
3343
|
+
if (hasData) expanded.push({
|
|
3344
|
+
version: op.version,
|
|
3345
|
+
updateDataModel: {
|
|
3346
|
+
surfaceId,
|
|
3347
|
+
path: "/",
|
|
3348
|
+
value: dataModel
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
if (hasComponents) expanded.push({
|
|
3352
|
+
version: op.version,
|
|
3353
|
+
updateComponents: {
|
|
3354
|
+
surfaceId,
|
|
3355
|
+
components
|
|
3356
|
+
}
|
|
3357
|
+
});
|
|
3358
|
+
return expanded;
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Each updateComponents becomes one operation per component, in order. The
|
|
3363
|
+
* complete components of a streaming line then give the same first
|
|
3364
|
+
* operations as the finished line, so the operation list only grows and the
|
|
3365
|
+
* manager never has to start the surface again.
|
|
3366
|
+
*/
|
|
3367
|
+
function splitComponentUpdates(operations) {
|
|
3368
|
+
return operations.flatMap((op) => {
|
|
3369
|
+
const update = op.updateComponents;
|
|
3370
|
+
if (!isRecord(update) || !Array.isArray(update.components)) return [op];
|
|
3371
|
+
if (update.components.length <= 1) return [op];
|
|
3372
|
+
return update.components.map((component) => ({
|
|
3373
|
+
...op,
|
|
3374
|
+
updateComponents: {
|
|
3375
|
+
...update,
|
|
3376
|
+
components: [component]
|
|
3377
|
+
}
|
|
3378
|
+
}));
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
/** createSurface first, the other operations after it in their order. */
|
|
3382
|
+
function createSurfaceFirst(operations) {
|
|
3383
|
+
const creates = operations.filter((op) => isRecord(op.createSurface));
|
|
3384
|
+
const others = operations.filter((op) => !isRecord(op.createSurface));
|
|
3385
|
+
return [...creates, ...others];
|
|
3386
|
+
}
|
|
3387
|
+
/** True when the component of `name` has the prop `prop`. */
|
|
3388
|
+
function hasProp(ui, name, prop) {
|
|
3389
|
+
const properties = ui.manifestComponents[name]?.props.properties;
|
|
3390
|
+
return isRecord(properties) && Object.hasOwn(properties, prop);
|
|
3391
|
+
}
|
|
3392
|
+
const childRefCache = /* @__PURE__ */ new WeakMap();
|
|
3393
|
+
/** The ids in a slot value: an id, a list of ids, or a template. */
|
|
3394
|
+
function slotIds(value) {
|
|
3395
|
+
if (typeof value === "string") return [value];
|
|
3396
|
+
if (Array.isArray(value)) return value.filter(isString);
|
|
3397
|
+
return isRecord(value) && isString(value.componentId) ? [value.componentId] : [];
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* A reader of the ids a component renders beyond `child` and `children`:
|
|
3401
|
+
* its declared slots (the `trigger` and `content` of a Modal) and its typed
|
|
3402
|
+
* child references (the `child` of each entry of `tabs`), from its catalog
|
|
3403
|
+
* entry. The prose lift follows these ids like `children`, so a panel is
|
|
3404
|
+
* never cut off from its Tabs. A reference that is bound to the data
|
|
3405
|
+
* model is "unknown": its ids exist only at render time.
|
|
3406
|
+
*/
|
|
3407
|
+
function childIdsReader(ui) {
|
|
3408
|
+
return (component) => {
|
|
3409
|
+
const name = component.component;
|
|
3410
|
+
const entry = typeof name === "string" && Object.hasOwn(ui.manifestComponents, name) ? ui.manifestComponents[name] : void 0;
|
|
3411
|
+
if (!entry) return [];
|
|
3412
|
+
const ids = [];
|
|
3413
|
+
for (const slot of Object.keys(entry.slots ?? {})) if (slot !== "child" && slot !== "children") ids.push(...slotIds(component[slot]));
|
|
3414
|
+
let refs = childRefCache.get(entry.props);
|
|
3415
|
+
if (!refs) {
|
|
3416
|
+
refs = (0, _copilotkit_shared.collectChildRefs)(entry.props);
|
|
3417
|
+
childRefCache.set(entry.props, refs);
|
|
3418
|
+
}
|
|
3419
|
+
if (refs.length === 0) return ids;
|
|
3420
|
+
if ((0, _copilotkit_shared.hasBoundChildRef)(component, refs)) return "unknown";
|
|
3421
|
+
return [...ids, ...(0, _copilotkit_shared.childIdsAtRefs)(component, refs)];
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
/** Every component of the operations by id, last definition wins. */
|
|
3425
|
+
function collectComponents(operations) {
|
|
3426
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3427
|
+
for (const op of operations) {
|
|
3428
|
+
const update = op.updateComponents;
|
|
3429
|
+
const list = isRecord(update) ? update.components : void 0;
|
|
3430
|
+
if (!Array.isArray(list)) continue;
|
|
3431
|
+
for (const component of list) if (isRecord(component) && isString(component.id)) byId.set(component.id, component);
|
|
3432
|
+
}
|
|
3433
|
+
return [...byId.values()];
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* The data model must not be shared between two surfaces. A data model
|
|
3437
|
+
* keeps the object it gets, so an input in one surface would change the
|
|
3438
|
+
* data of the other one.
|
|
3439
|
+
*/
|
|
3440
|
+
function cloneData(value) {
|
|
3441
|
+
return value === void 0 ? void 0 : structuredClone(value);
|
|
3442
|
+
}
|
|
3443
|
+
/** The operation sent to `surfaceId`, with a copy of its data model value. */
|
|
3444
|
+
function readdress(op, surfaceId) {
|
|
3445
|
+
const kind = (0, _copilotkit_shared.getOperationKind)(op);
|
|
3446
|
+
const payload = kind ? op[kind] : void 0;
|
|
3447
|
+
if (!kind || !isRecord(payload)) return op;
|
|
3448
|
+
const next = {
|
|
3449
|
+
...payload,
|
|
3450
|
+
surfaceId
|
|
3451
|
+
};
|
|
3452
|
+
if (kind === "updateDataModel" && "value" in payload) next.value = cloneData(payload.value);
|
|
3453
|
+
return {
|
|
3454
|
+
...op,
|
|
3455
|
+
[kind]: next
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* The operations of one sub-surface. Each updateComponents keeps only the
|
|
3460
|
+
* components of the sub-surface, with its own root. The other operations
|
|
3461
|
+
* are sent to the sub-surface. The order stays the same, so a new operation
|
|
3462
|
+
* of the fence only adds operations at the end.
|
|
3463
|
+
*/
|
|
3464
|
+
function subSurfaceOperations(operations, components, surfaceId) {
|
|
3465
|
+
const root = components.find((c) => c.id === _copilotkit_shared.GENERATIVE_UI_ROOT_ID);
|
|
3466
|
+
const ids = new Set(components.map((c) => c.id));
|
|
3467
|
+
const out = [];
|
|
3468
|
+
for (const op of operations) {
|
|
3469
|
+
const update = op.updateComponents;
|
|
3470
|
+
if (!isRecord(update) || !Array.isArray(update.components)) {
|
|
3471
|
+
out.push(readdress(op, surfaceId));
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
const kept = update.components.filter((c) => isRecord(c) && ids.has(c.id)).map((c) => isRecord(c) && c.id === _copilotkit_shared.GENERATIVE_UI_ROOT_ID ? root : c);
|
|
3475
|
+
if (kept.length === 0) continue;
|
|
3476
|
+
out.push({
|
|
3477
|
+
...op,
|
|
3478
|
+
updateComponents: {
|
|
3479
|
+
...update,
|
|
3480
|
+
surfaceId,
|
|
3481
|
+
components: kept
|
|
3482
|
+
}
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
return out;
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* Prose lift. Text components in the main stack of the surface (titles,
|
|
3489
|
+
* paragraphs, text lists and the dividers between them) become chat
|
|
3490
|
+
* markdown. The surface splits around them: the first surface keeps
|
|
3491
|
+
* `surfaceId`, the next ones are `<surfaceId>#1`, `<surfaceId>#2`, and so
|
|
3492
|
+
* on. A surface with no prose comes back as one part with the same
|
|
3493
|
+
* operations.
|
|
3494
|
+
*/
|
|
3495
|
+
function liftProse(operations, surfaceId, childIds) {
|
|
3496
|
+
const components = collectComponents(operations);
|
|
3497
|
+
const segments = (0, _copilotkit_core.segmentSurfaceProse)(components, { childIds });
|
|
3498
|
+
const [first] = segments;
|
|
3499
|
+
if (segments.length === 1 && first?.kind === "surface" && first.components === components) return [{
|
|
3500
|
+
kind: "surface",
|
|
3501
|
+
surfaceId,
|
|
3502
|
+
operations
|
|
3503
|
+
}];
|
|
3504
|
+
let surfaceIndex = 0;
|
|
3505
|
+
return segments.map((segment) => {
|
|
3506
|
+
if (segment.kind === "markdown") return segment;
|
|
3507
|
+
const id = surfaceIndex === 0 ? surfaceId : `${surfaceId}#${surfaceIndex}`;
|
|
3508
|
+
surfaceIndex++;
|
|
3509
|
+
return {
|
|
3510
|
+
kind: "surface",
|
|
3511
|
+
surfaceId: id,
|
|
3512
|
+
operations: subSurfaceOperations(operations, segment.components, id)
|
|
3513
|
+
};
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
/** `validateOperation`, with the operation typed when it is valid. */
|
|
3517
|
+
function checkOperation(op, options) {
|
|
3518
|
+
const validation = (0, _copilotkit_shared.validateOperation)(op, options);
|
|
3519
|
+
if (!validation.ok) return validation;
|
|
3520
|
+
return {
|
|
3521
|
+
ok: true,
|
|
3522
|
+
operation: op
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* The content of one surface. Each operation is validated against the
|
|
3527
|
+
* manifest. A valid operation goes in `operations`. A rejected one goes in
|
|
3528
|
+
* `rejected`, with its position and the reason.
|
|
3529
|
+
*/
|
|
3530
|
+
function surfaceContent(surfaceId, draft, operations, options) {
|
|
3531
|
+
const { ui, functions } = options;
|
|
3532
|
+
const knownSurfaces = /* @__PURE__ */ new Set();
|
|
3533
|
+
const manifest = {
|
|
3534
|
+
catalogId: ui.id,
|
|
3535
|
+
catalogIds: catalogIdsOf(ui),
|
|
3536
|
+
components: ui.manifestComponents
|
|
3537
|
+
};
|
|
3538
|
+
const valid = [];
|
|
3539
|
+
const rejected = [];
|
|
3540
|
+
operations.forEach((op, index) => {
|
|
3541
|
+
const result = checkOperation(op, {
|
|
3542
|
+
manifest,
|
|
3543
|
+
knownSurfaces,
|
|
3544
|
+
functions
|
|
3545
|
+
});
|
|
3546
|
+
if (!result.ok) {
|
|
3547
|
+
rejected.push({
|
|
3548
|
+
index,
|
|
3549
|
+
reason: result.reason
|
|
3550
|
+
});
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
valid.push(result.operation);
|
|
3554
|
+
const kind = (0, _copilotkit_shared.getOperationKind)(result.operation);
|
|
3555
|
+
if (kind === "createSurface") knownSurfaces.add(surfaceId);
|
|
3556
|
+
if (kind === "deleteSurface") knownSurfaces.delete(surfaceId);
|
|
3557
|
+
});
|
|
3558
|
+
const create = valid.find((op) => "createSurface" in op);
|
|
3559
|
+
return {
|
|
3560
|
+
surfaceId,
|
|
3561
|
+
target: draft.target,
|
|
3562
|
+
catalogId: create?.createSurface.catalogId ?? ui.id,
|
|
3563
|
+
operations: valid,
|
|
3564
|
+
status: draft.streaming ? "streaming" : "ready",
|
|
3565
|
+
rejected
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* Builds the surfaces of a thread from the ```a2ui fences of its assistant
|
|
3570
|
+
* messages, in message order.
|
|
3571
|
+
*
|
|
3572
|
+
* - Operations are grouped by surfaceId across all messages. An operation
|
|
3573
|
+
* with no surfaceId goes to the first surface of its fence, else to
|
|
3574
|
+
* `<messageId>-<fenceIndex>`.
|
|
3575
|
+
* - The fence that first writes a surface creates it. The `target=<id>` of
|
|
3576
|
+
* that fence sets the target. With no target, the surface goes to the
|
|
3577
|
+
* chat.
|
|
3578
|
+
* - A later fence of the same reply for the same surfaceId updates it. A
|
|
3579
|
+
* later reply that reuses the id of a chat surface gets a surface of its
|
|
3580
|
+
* own, `<id>@<messageId>`. A page target is updated from any reply.
|
|
3581
|
+
* - The status is "streaming" while the creating fence streams, else
|
|
3582
|
+
* "ready".
|
|
3583
|
+
* - Each operation is validated with `validateOperation`. Rejected ones are
|
|
3584
|
+
* left out and listed in `rejected`.
|
|
3585
|
+
* - Chat surfaces get the prose lift: informational cards lose their box,
|
|
3586
|
+
* and prose becomes markdown that renders between sub-surfaces
|
|
3587
|
+
* (`<surfaceId>#<n>`). An inline createSurface is split into its
|
|
3588
|
+
* createSurface, updateDataModel and updateComponents first.
|
|
3589
|
+
* - A `callRendererFunction` line is not part of a surface. It goes to
|
|
3590
|
+
* `functionCalls`. A streaming line gives nothing, so a function never
|
|
3591
|
+
* runs with a part of its args.
|
|
3592
|
+
*
|
|
3593
|
+
* The function is pure. Call it again on each stream update. The manager
|
|
3594
|
+
* applies only the new operations, so unchanged surfaces do not render
|
|
3595
|
+
* again.
|
|
3596
|
+
*
|
|
3597
|
+
* @param messages - The messages of one thread.
|
|
3598
|
+
* @param options - The generative UI, its declared functions, and whether
|
|
3599
|
+
* the last message can still grow.
|
|
3600
|
+
* @returns `surfaces`, one content for each surface and sub-surface;
|
|
3601
|
+
* `fences`, the blocks that each fence renders, by `fenceKey` (a fence
|
|
3602
|
+
* that is not in `fences` renders nothing); `functionCalls`, the function
|
|
3603
|
+
* calls of the fences in order; and `parts`, the surface ids of the parts
|
|
3604
|
+
* of each surface, by surface id.
|
|
3605
|
+
*
|
|
3606
|
+
* @example
|
|
3607
|
+
* ```ts
|
|
3608
|
+
* const { surfaces } = buildMarkdownSurfaces(agent.messages, {
|
|
3609
|
+
* ui,
|
|
3610
|
+
* functions: {},
|
|
3611
|
+
* running: agent.isRunning,
|
|
3612
|
+
* });
|
|
3613
|
+
* for (const content of surfaces) manager.ingest(threadId, agentId, content);
|
|
3614
|
+
* ```
|
|
3615
|
+
*/
|
|
3616
|
+
function buildMarkdownSurfaces(messages, options) {
|
|
3617
|
+
const { ui, cache } = options;
|
|
3618
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
3619
|
+
const readdressOnce = (op, surfaceId) => {
|
|
3620
|
+
if (!cache) return readdress(op, surfaceId);
|
|
3621
|
+
let copies = cache.readdressed.get(op);
|
|
3622
|
+
if (!copies) {
|
|
3623
|
+
copies = /* @__PURE__ */ new Map();
|
|
3624
|
+
cache.readdressed.set(op, copies);
|
|
3625
|
+
}
|
|
3626
|
+
let copy = copies.get(surfaceId);
|
|
3627
|
+
if (!copy) {
|
|
3628
|
+
copy = readdress(op, surfaceId);
|
|
3629
|
+
copies.set(surfaceId, copy);
|
|
3630
|
+
}
|
|
3631
|
+
return copy;
|
|
3632
|
+
};
|
|
3633
|
+
/** The surfaces that each fence created, in order. */
|
|
3634
|
+
const created = /* @__PURE__ */ new Map();
|
|
3635
|
+
const functionCalls = [];
|
|
3636
|
+
/** The message that owns each chat surface id, by the id the model wrote. */
|
|
3637
|
+
const chatOwners = /* @__PURE__ */ new Map();
|
|
3638
|
+
const lastMessage = messages[messages.length - 1];
|
|
3639
|
+
/**
|
|
3640
|
+
* A chat surface belongs to the reply that wrote it: the chat is a
|
|
3641
|
+
* transcript, so a later reply that reuses an id gets a surface of its
|
|
3642
|
+
* own (`<id>@<messageId>`) instead of changing the earlier reply. A fence
|
|
3643
|
+
* for a page target keeps its id, because a target is a place on the
|
|
3644
|
+
* page that a later reply updates on purpose.
|
|
3645
|
+
*/
|
|
3646
|
+
const scopedSurfaceId = (requested, messageId, target) => {
|
|
3647
|
+
if (target !== void 0) return requested;
|
|
3648
|
+
const owner = chatOwners.get(requested);
|
|
3649
|
+
if (owner === void 0) {
|
|
3650
|
+
chatOwners.set(requested, messageId);
|
|
3651
|
+
return requested;
|
|
3652
|
+
}
|
|
3653
|
+
return owner === messageId ? requested : `${requested}@${messageId}`;
|
|
3654
|
+
};
|
|
3655
|
+
for (const message of messages) {
|
|
3656
|
+
if (message.role !== "assistant" || !isString(message.content)) continue;
|
|
3657
|
+
const isLastMessage = message === lastMessage;
|
|
3658
|
+
const content = message.content;
|
|
3659
|
+
const segments = cache ? cache.segments.get(content, () => (0, _copilotkit_core.splitA2UIMarkdown)(content)) : (0, _copilotkit_core.splitA2UIMarkdown)(content);
|
|
3660
|
+
for (const segment of segments) {
|
|
3661
|
+
if (segment.type !== "a2ui") continue;
|
|
3662
|
+
const isStreaming = !segment.closed && isLastMessage && options.running;
|
|
3663
|
+
const parseFence = () => {
|
|
3664
|
+
const parsed = fenceOperations(segment.text, isStreaming);
|
|
3665
|
+
const { functionCalls } = (0, _copilotkit_core.prepareSurfaceOperations)({
|
|
3666
|
+
operations: parsed,
|
|
3667
|
+
fallbackSurfaceId: "",
|
|
3668
|
+
catalogId: ui.id
|
|
3669
|
+
});
|
|
3670
|
+
return {
|
|
3671
|
+
operations: parsed,
|
|
3672
|
+
functionCalls
|
|
3673
|
+
};
|
|
3674
|
+
};
|
|
3675
|
+
const fence = cache ? cache.fences.get(`${isStreaming}:${segment.text}`, parseFence) : parseFence();
|
|
3676
|
+
const { operations } = fence;
|
|
3677
|
+
const key = fenceKey(message.id, segment.index);
|
|
3678
|
+
const fenceSurfaceId = operations.map(_copilotkit_core.getMarkdownOperationSurfaceId).find(isString) ?? `${message.id}-${segment.index}`;
|
|
3679
|
+
for (const operation of fence.functionCalls) functionCalls.push({
|
|
3680
|
+
messageId: message.id,
|
|
3681
|
+
fenceIndex: segment.index,
|
|
3682
|
+
operation
|
|
3683
|
+
});
|
|
3684
|
+
const surfaceOperations = operations.filter((op) => (0, _copilotkit_shared.getOperationKind)(op) !== "callRendererFunction");
|
|
3685
|
+
for (const op of surfaceOperations) {
|
|
3686
|
+
const requested = (0, _copilotkit_core.getMarkdownOperationSurfaceId)(op) ?? fenceSurfaceId;
|
|
3687
|
+
const surfaceId = scopedSurfaceId(requested, message.id, segment.target);
|
|
3688
|
+
let draft = drafts.get(surfaceId);
|
|
3689
|
+
if (!draft) {
|
|
3690
|
+
draft = {
|
|
3691
|
+
surfaceId,
|
|
3692
|
+
target: segment.target ?? _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET,
|
|
3693
|
+
operations: [],
|
|
3694
|
+
streaming: isStreaming
|
|
3695
|
+
};
|
|
3696
|
+
drafts.set(surfaceId, draft);
|
|
3697
|
+
created.set(key, [...created.get(key) ?? [], surfaceId]);
|
|
3698
|
+
}
|
|
3699
|
+
draft.operations.push(surfaceId === requested ? op : readdressOnce(op, surfaceId));
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
const canFlattenCards = hasProp(ui, "Card", "variant");
|
|
3704
|
+
const surfaces = [];
|
|
3705
|
+
const partsBySurface = /* @__PURE__ */ new Map();
|
|
3706
|
+
const childIds = childIdsReader(ui);
|
|
3707
|
+
const buildDraft = (draft) => {
|
|
3708
|
+
const operations = splitComponentUpdates(expandInlineCreate(createSurfaceFirst((0, _copilotkit_core.prepareSurfaceOperations)({
|
|
3709
|
+
operations: draft.operations,
|
|
3710
|
+
fallbackSurfaceId: draft.surfaceId,
|
|
3711
|
+
catalogId: ui.id,
|
|
3712
|
+
catalogIds: catalogIdsOf(ui)
|
|
3713
|
+
}).operations)));
|
|
3714
|
+
const parts = draft.target === _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET ? liftProse(canFlattenCards ? (0, _copilotkit_core.flattenInformationalCards)(operations, { childIds }) : operations, draft.surfaceId, childIds) : [{
|
|
3715
|
+
kind: "surface",
|
|
3716
|
+
surfaceId: draft.surfaceId,
|
|
3717
|
+
operations
|
|
3718
|
+
}];
|
|
3719
|
+
const contents = parts.flatMap((part) => part.kind === "surface" ? [surfaceContent(part.surfaceId, draft, part.operations, options)] : []);
|
|
3720
|
+
return {
|
|
3721
|
+
...draft,
|
|
3722
|
+
parts,
|
|
3723
|
+
contents
|
|
3724
|
+
};
|
|
3725
|
+
};
|
|
3726
|
+
for (const draft of drafts.values()) {
|
|
3727
|
+
const built = cache ? cache.drafts.get(draft.surfaceId, () => buildDraft(draft), (last) => last.target === draft.target && last.streaming === draft.streaming && sameItems(last.operations, draft.operations)) : buildDraft(draft);
|
|
3728
|
+
partsBySurface.set(draft.surfaceId, built.parts);
|
|
3729
|
+
surfaces.push(...built.contents);
|
|
3730
|
+
}
|
|
3731
|
+
cache?.endBuild();
|
|
3732
|
+
/** The surface ids of the parts of each split surface, by surface id. */
|
|
3733
|
+
const parts = /* @__PURE__ */ new Map();
|
|
3734
|
+
for (const [surfaceId, surfaceParts] of partsBySurface) {
|
|
3735
|
+
const ids = surfaceParts.flatMap((part) => part.kind === "surface" ? [part.surfaceId] : []);
|
|
3736
|
+
parts.set(surfaceId, ids);
|
|
3737
|
+
}
|
|
3738
|
+
const fences = /* @__PURE__ */ new Map();
|
|
3739
|
+
for (const [key, surfaceIds] of created) {
|
|
3740
|
+
const blocks = [];
|
|
3741
|
+
for (const surfaceId of surfaceIds) {
|
|
3742
|
+
if (drafts.get(surfaceId)?.target !== _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET) continue;
|
|
3743
|
+
for (const part of partsBySurface.get(surfaceId) ?? []) blocks.push(part.kind === "markdown" ? part : {
|
|
3744
|
+
kind: "surface",
|
|
3745
|
+
surfaceId: part.surfaceId
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
if (blocks.length > 0) fences.set(key, blocks);
|
|
3749
|
+
}
|
|
3750
|
+
return {
|
|
3751
|
+
surfaces,
|
|
3752
|
+
fences,
|
|
3753
|
+
functionCalls,
|
|
3754
|
+
parts
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
/** The declared functions, as `validateOperation` takes them. */
|
|
3758
|
+
function declaredFunctions(ui) {
|
|
3759
|
+
const functions = {};
|
|
3760
|
+
for (const [name, fn] of ui.functions) try {
|
|
3761
|
+
functions[name] = {
|
|
3762
|
+
...fn.description ? { description: fn.description } : {},
|
|
3763
|
+
parameters: toJsonSchema(fn.parameters)
|
|
3764
|
+
};
|
|
3765
|
+
} catch {
|
|
3766
|
+
functions[name] = { parameters: {} };
|
|
3767
|
+
}
|
|
3768
|
+
return functions;
|
|
3769
|
+
}
|
|
3770
|
+
/** True when `list` starts with all the items of `prefix`. */
|
|
3771
|
+
function startsWith(list, prefix) {
|
|
3772
|
+
return list.length >= prefix.length && prefix.every((item, index) => item === list[index]);
|
|
3773
|
+
}
|
|
3774
|
+
/** A content that deletes a surface that the fences no longer write. */
|
|
3775
|
+
function removalContent(surfaceId, catalogId) {
|
|
3776
|
+
return {
|
|
3777
|
+
surfaceId,
|
|
3778
|
+
target: _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET,
|
|
3779
|
+
catalogId,
|
|
3780
|
+
status: "ready",
|
|
3781
|
+
rejected: [],
|
|
3782
|
+
operations: [{
|
|
3783
|
+
version: _copilotkit_shared.A2UI_VERSION,
|
|
3784
|
+
createSurface: {
|
|
3785
|
+
surfaceId,
|
|
3786
|
+
catalogId
|
|
3787
|
+
}
|
|
3788
|
+
}, {
|
|
3789
|
+
version: _copilotkit_shared.A2UI_VERSION,
|
|
3790
|
+
deleteSurface: { surfaceId }
|
|
3791
|
+
}]
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
/**
|
|
3795
|
+
* The markdown transport source of one surface manager. It turns the
|
|
3796
|
+
* messages of a thread into surfaces in the manager, and keeps the blocks
|
|
3797
|
+
* that each fence renders.
|
|
3798
|
+
*/
|
|
3799
|
+
var MarkdownSurfaceSource = class {
|
|
3800
|
+
constructor(manager, ui) {
|
|
3801
|
+
this.manager = manager;
|
|
3802
|
+
this.ui = ui;
|
|
3803
|
+
this.fences = /* @__PURE__ */ new Map();
|
|
3804
|
+
this.ingested = /* @__PURE__ */ new Map();
|
|
3805
|
+
this.contents = /* @__PURE__ */ new Map();
|
|
3806
|
+
this.caches = /* @__PURE__ */ new Map();
|
|
3807
|
+
this.functions = declaredFunctions(ui);
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Builds the surfaces of `threadId` from `messages` and gives them to the
|
|
3811
|
+
* manager. A surface that an earlier sync of the thread made, and that the
|
|
3812
|
+
* messages no longer write, is deleted. Other threads do not change.
|
|
3813
|
+
*
|
|
3814
|
+
* @param threadId - The thread of the messages.
|
|
3815
|
+
* @param agentId - The agent that owns the thread.
|
|
3816
|
+
* @param messages - All messages of the thread.
|
|
3817
|
+
* @param options - `running`: the last message can still grow.
|
|
3818
|
+
* @returns The function calls of the fences, in order. The source does
|
|
3819
|
+
* not run them.
|
|
3820
|
+
*/
|
|
3821
|
+
sync(threadId, agentId, messages, options) {
|
|
3822
|
+
let cache = this.caches.get(threadId);
|
|
3823
|
+
if (!cache) {
|
|
3824
|
+
cache = new MarkdownBuildCache();
|
|
3825
|
+
this.caches.set(threadId, cache);
|
|
3826
|
+
}
|
|
3827
|
+
const result = buildMarkdownSurfaces(messages, {
|
|
3828
|
+
ui: this.ui,
|
|
3829
|
+
functions: this.functions,
|
|
3830
|
+
running: options.running,
|
|
3831
|
+
cache
|
|
3832
|
+
});
|
|
3833
|
+
this.fences.set(threadId, result.fences);
|
|
3834
|
+
for (const parts of result.parts.values()) this.manager.linkSurfaces(threadId, parts);
|
|
3835
|
+
const previous = this.ingested.get(threadId) ?? /* @__PURE__ */ new Map();
|
|
3836
|
+
const previousContents = this.contents.get(threadId) ?? /* @__PURE__ */ new Map();
|
|
3837
|
+
const next = /* @__PURE__ */ new Map();
|
|
3838
|
+
const nextContents = /* @__PURE__ */ new Map();
|
|
3839
|
+
for (const content of result.surfaces) {
|
|
3840
|
+
nextContents.set(content.surfaceId, content);
|
|
3841
|
+
const before = previous.get(content.surfaceId);
|
|
3842
|
+
if (before && previousContents.get(content.surfaceId) === content) {
|
|
3843
|
+
next.set(content.surfaceId, before);
|
|
3844
|
+
continue;
|
|
3845
|
+
}
|
|
3846
|
+
const fingerprints = content.operations.map((op) => JSON.stringify(op));
|
|
3847
|
+
if (before && !startsWith(fingerprints, before)) this.manager.ingest(threadId, agentId, {
|
|
3848
|
+
...content,
|
|
3849
|
+
operations: []
|
|
3850
|
+
});
|
|
3851
|
+
this.manager.ingest(threadId, agentId, content);
|
|
3852
|
+
next.set(content.surfaceId, fingerprints);
|
|
3853
|
+
}
|
|
3854
|
+
for (const surfaceId of previous.keys()) {
|
|
3855
|
+
if (next.has(surfaceId)) continue;
|
|
3856
|
+
this.manager.ingest(threadId, agentId, removalContent(surfaceId, this.ui.id));
|
|
3857
|
+
}
|
|
3858
|
+
this.ingested.set(threadId, next);
|
|
3859
|
+
this.contents.set(threadId, nextContents);
|
|
3860
|
+
return result.functionCalls;
|
|
3861
|
+
}
|
|
3862
|
+
/**
|
|
3863
|
+
* The blocks that a fence renders in the chat, from the latest sync of the
|
|
3864
|
+
* thread. `undefined` when the fence renders nothing: it updates a surface
|
|
3865
|
+
* of an earlier fence, its surface is for a named target, or it has no
|
|
3866
|
+
* complete operation yet.
|
|
3867
|
+
*/
|
|
3868
|
+
getFenceBlocks(threadId, messageId, fenceIndex) {
|
|
3869
|
+
return this.fences.get(threadId)?.get(fenceKey(messageId, fenceIndex));
|
|
3870
|
+
}
|
|
3871
|
+
};
|
|
3872
|
+
const sources = /* @__PURE__ */ new WeakMap();
|
|
3873
|
+
/**
|
|
3874
|
+
* The markdown source of a generative UI runtime. There is one source for
|
|
3875
|
+
* each surface manager. The root syncs it, and the chat messages read the
|
|
3876
|
+
* fence blocks from it.
|
|
3877
|
+
*/
|
|
3878
|
+
function getMarkdownSurfaceSource(runtime) {
|
|
3879
|
+
let source = sources.get(runtime.manager);
|
|
3880
|
+
if (!source) {
|
|
3881
|
+
source = new MarkdownSurfaceSource(runtime.manager, runtime.ui);
|
|
3882
|
+
sources.set(runtime.manager, source);
|
|
3883
|
+
}
|
|
3884
|
+
return source;
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
//#endregion
|
|
3888
|
+
//#region src/v2/generative-ui/calc.ts
|
|
3889
|
+
const MAX_SERIES = 1e3;
|
|
3890
|
+
const OPERATORS = [
|
|
3891
|
+
"&&",
|
|
3892
|
+
"||",
|
|
3893
|
+
"==",
|
|
3894
|
+
"!=",
|
|
3895
|
+
"<=",
|
|
3896
|
+
">=",
|
|
3897
|
+
"<",
|
|
3898
|
+
">",
|
|
3899
|
+
"+",
|
|
3900
|
+
"-",
|
|
3901
|
+
"*",
|
|
3902
|
+
"/",
|
|
3903
|
+
"%",
|
|
3904
|
+
"(",
|
|
3905
|
+
")",
|
|
3906
|
+
",",
|
|
3907
|
+
"?",
|
|
3908
|
+
":",
|
|
3909
|
+
"!"
|
|
3910
|
+
];
|
|
3911
|
+
function tokenize(source) {
|
|
3912
|
+
const tokens = [];
|
|
3913
|
+
let i = 0;
|
|
3914
|
+
while (i < source.length) {
|
|
3915
|
+
const char = source[i];
|
|
3916
|
+
if (/\s/.test(char)) {
|
|
3917
|
+
i++;
|
|
3918
|
+
continue;
|
|
3919
|
+
}
|
|
3920
|
+
if (/[0-9.]/.test(char)) {
|
|
3921
|
+
const match = /^\d*\.?\d+(?:[eE][-+]?\d+)?|^\d+\.?/.exec(source.slice(i));
|
|
3922
|
+
if (!match) throw new ExpressionError(`calc: bad number at "${source.slice(i, i + 8)}"`);
|
|
3923
|
+
tokens.push({
|
|
3924
|
+
kind: "number",
|
|
3925
|
+
value: Number(match[0])
|
|
3926
|
+
});
|
|
3927
|
+
i += match[0].length;
|
|
3928
|
+
continue;
|
|
3929
|
+
}
|
|
3930
|
+
if (char === "'" || char === "\"") {
|
|
3931
|
+
let value = "";
|
|
3932
|
+
i++;
|
|
3933
|
+
while (i < source.length && source[i] !== char) {
|
|
3934
|
+
if (source[i] === "\\" && i + 1 < source.length) i++;
|
|
3935
|
+
value += source[i++];
|
|
3936
|
+
}
|
|
3937
|
+
if (i >= source.length) throw new ExpressionError("calc: unclosed string");
|
|
3938
|
+
i++;
|
|
3939
|
+
tokens.push({
|
|
3940
|
+
kind: "string",
|
|
3941
|
+
value
|
|
3942
|
+
});
|
|
3943
|
+
continue;
|
|
3944
|
+
}
|
|
3945
|
+
if (/[A-Za-z_]/.test(char)) {
|
|
3946
|
+
const match = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(i));
|
|
3947
|
+
tokens.push({
|
|
3948
|
+
kind: "name",
|
|
3949
|
+
value: match[0]
|
|
3950
|
+
});
|
|
3951
|
+
i += match[0].length;
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
const op = OPERATORS.find((candidate) => source.startsWith(candidate, i));
|
|
3955
|
+
if (!op) throw new ExpressionError(`calc: unexpected "${char}"`);
|
|
3956
|
+
tokens.push({
|
|
3957
|
+
kind: "op",
|
|
3958
|
+
value: op
|
|
3959
|
+
});
|
|
3960
|
+
i += op.length;
|
|
3961
|
+
}
|
|
3962
|
+
tokens.push({ kind: "end" });
|
|
3963
|
+
return tokens;
|
|
3964
|
+
}
|
|
3965
|
+
function numberOf(value, what) {
|
|
3966
|
+
if (typeof value === "number") {
|
|
3967
|
+
if (!Number.isFinite(value)) throw new ExpressionError(`calc: ${what} is not finite`);
|
|
3968
|
+
return value;
|
|
3969
|
+
}
|
|
3970
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
3971
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3972
|
+
const parsed = Number(value);
|
|
3973
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
3974
|
+
}
|
|
3975
|
+
if (value === null || value === void 0) throw new ExpressionError(`calc: ${what} has no value`);
|
|
3976
|
+
throw new ExpressionError(`calc: ${what} is not a number`);
|
|
3977
|
+
}
|
|
3978
|
+
function describe(value) {
|
|
3979
|
+
return typeof value === "string" ? `"${value}"` : String(value);
|
|
3980
|
+
}
|
|
3981
|
+
const MATH = {
|
|
3982
|
+
min: (args) => Math.min(...args),
|
|
3983
|
+
max: (args) => Math.max(...args),
|
|
3984
|
+
abs: ([x]) => Math.abs(x),
|
|
3985
|
+
floor: ([x]) => Math.floor(x),
|
|
3986
|
+
ceil: ([x]) => Math.ceil(x),
|
|
3987
|
+
round: ([x, decimals = 0]) => {
|
|
3988
|
+
const factor = 10 ** Math.trunc(decimals);
|
|
3989
|
+
return Math.round(x * factor) / factor;
|
|
3990
|
+
}
|
|
3991
|
+
};
|
|
3992
|
+
var CalcParser = class {
|
|
3993
|
+
constructor(tokens, scope, source) {
|
|
3994
|
+
this.tokens = tokens;
|
|
3995
|
+
this.scope = scope;
|
|
3996
|
+
this.source = source;
|
|
3997
|
+
this.pos = 0;
|
|
3998
|
+
this.skipDepth = 0;
|
|
3999
|
+
}
|
|
4000
|
+
parse() {
|
|
4001
|
+
const value = this.ternary();
|
|
4002
|
+
if (this.tokens[this.pos].kind !== "end") this.fail(`unexpected "${this.text(this.tokens[this.pos])}"`);
|
|
4003
|
+
return value;
|
|
4004
|
+
}
|
|
4005
|
+
fail(what) {
|
|
4006
|
+
throw new ExpressionError(`calc: ${what} in "${this.source}"`);
|
|
4007
|
+
}
|
|
4008
|
+
text(token) {
|
|
4009
|
+
return token.kind === "end" ? "end" : String(token.value);
|
|
4010
|
+
}
|
|
4011
|
+
peekOp(...ops) {
|
|
4012
|
+
const token = this.tokens[this.pos];
|
|
4013
|
+
return token.kind === "op" && ops.includes(token.value) ? token.value : void 0;
|
|
4014
|
+
}
|
|
4015
|
+
expectOp(op) {
|
|
4016
|
+
if (this.peekOp(op) === void 0) this.fail(`expected "${op}" but found "${this.text(this.tokens[this.pos])}"`);
|
|
4017
|
+
this.pos++;
|
|
4018
|
+
}
|
|
4019
|
+
/**
|
|
4020
|
+
* Parses a part of the expression that the result does not need (the
|
|
4021
|
+
* other branch of `?:`, the right side of a decided `&&` or `||`). It
|
|
4022
|
+
* still has to be parsed to find where it ends, so syntax errors are
|
|
4023
|
+
* reported, but no value is checked or computed: `n == 0 ? 0 : 10 / n`
|
|
4024
|
+
* is a valid guard.
|
|
4025
|
+
*/
|
|
4026
|
+
skipped(parse) {
|
|
4027
|
+
this.skipDepth++;
|
|
4028
|
+
try {
|
|
4029
|
+
parse();
|
|
4030
|
+
} finally {
|
|
4031
|
+
this.skipDepth--;
|
|
4032
|
+
}
|
|
4033
|
+
return null;
|
|
4034
|
+
}
|
|
4035
|
+
/** The number of a value, or 0 while skipping. */
|
|
4036
|
+
num(value, what) {
|
|
4037
|
+
return this.skipDepth > 0 ? 0 : numberOf(value, what);
|
|
4038
|
+
}
|
|
4039
|
+
ternary() {
|
|
4040
|
+
const condition = this.or();
|
|
4041
|
+
if (this.peekOp("?") === void 0) return condition;
|
|
4042
|
+
this.pos++;
|
|
4043
|
+
const takesFirst = truthOf(condition);
|
|
4044
|
+
const whenTrue = takesFirst ? this.ternary() : this.skipped(() => this.ternary());
|
|
4045
|
+
this.expectOp(":");
|
|
4046
|
+
const whenFalse = takesFirst ? this.skipped(() => this.ternary()) : this.ternary();
|
|
4047
|
+
return takesFirst ? whenTrue : whenFalse;
|
|
4048
|
+
}
|
|
4049
|
+
or() {
|
|
4050
|
+
let left = this.and();
|
|
4051
|
+
while (this.peekOp("||")) {
|
|
4052
|
+
this.pos++;
|
|
4053
|
+
if (truthOf(left)) {
|
|
4054
|
+
this.skipped(() => this.and());
|
|
4055
|
+
left = true;
|
|
4056
|
+
} else left = truthOf(this.and());
|
|
4057
|
+
}
|
|
4058
|
+
return left;
|
|
4059
|
+
}
|
|
4060
|
+
and() {
|
|
4061
|
+
let left = this.equality();
|
|
4062
|
+
while (this.peekOp("&&")) {
|
|
4063
|
+
this.pos++;
|
|
4064
|
+
if (!truthOf(left)) {
|
|
4065
|
+
this.skipped(() => this.equality());
|
|
4066
|
+
left = false;
|
|
4067
|
+
} else left = truthOf(this.equality());
|
|
4068
|
+
}
|
|
4069
|
+
return left;
|
|
4070
|
+
}
|
|
4071
|
+
equality() {
|
|
4072
|
+
let left = this.comparison();
|
|
4073
|
+
for (let op = this.peekOp("==", "!="); op; op = this.peekOp("==", "!=")) {
|
|
4074
|
+
this.pos++;
|
|
4075
|
+
const right = this.comparison();
|
|
4076
|
+
const isEqual = typeof left === "number" || typeof right === "number" ? this.num(left, "the left side of ==") === this.num(right, "the right side of ==") : left === right;
|
|
4077
|
+
left = op === "==" ? isEqual : !isEqual;
|
|
4078
|
+
}
|
|
4079
|
+
return left;
|
|
4080
|
+
}
|
|
4081
|
+
comparison() {
|
|
4082
|
+
let left = this.additive();
|
|
4083
|
+
for (let op = this.peekOp("<", "<=", ">", ">="); op; op = this.peekOp("<", "<=", ">", ">=")) {
|
|
4084
|
+
this.pos++;
|
|
4085
|
+
const a = this.num(left, `the left side of ${op}`);
|
|
4086
|
+
const b = this.num(this.additive(), `the right side of ${op}`);
|
|
4087
|
+
left = op === "<" ? a < b : op === "<=" ? a <= b : op === ">" ? a > b : a >= b;
|
|
4088
|
+
}
|
|
4089
|
+
return left;
|
|
4090
|
+
}
|
|
4091
|
+
additive() {
|
|
4092
|
+
let left = this.multiplicative();
|
|
4093
|
+
for (let op = this.peekOp("+", "-"); op; op = this.peekOp("+", "-")) {
|
|
4094
|
+
this.pos++;
|
|
4095
|
+
const right = this.multiplicative();
|
|
4096
|
+
if (op === "+" && (typeof left === "string" || typeof right === "string")) left = `${left ?? ""}${right ?? ""}`;
|
|
4097
|
+
else {
|
|
4098
|
+
const a = this.num(left, `the left side of ${op}`);
|
|
4099
|
+
const b = this.num(right, `the right side of ${op}`);
|
|
4100
|
+
left = op === "+" ? a + b : a - b;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
return left;
|
|
4104
|
+
}
|
|
4105
|
+
multiplicative() {
|
|
4106
|
+
let left = this.unary();
|
|
4107
|
+
for (let op = this.peekOp("*", "/", "%"); op; op = this.peekOp("*", "/", "%")) {
|
|
4108
|
+
this.pos++;
|
|
4109
|
+
const a = this.num(left, `the left side of ${op}`);
|
|
4110
|
+
const b = this.num(this.unary(), `the right side of ${op}`);
|
|
4111
|
+
if (op !== "*" && b === 0 && this.skipDepth === 0) this.fail("division by zero");
|
|
4112
|
+
left = op === "*" ? a * b : op === "/" ? a / b : a % b;
|
|
4113
|
+
}
|
|
4114
|
+
return left;
|
|
4115
|
+
}
|
|
4116
|
+
unary() {
|
|
4117
|
+
if (this.peekOp("-")) {
|
|
4118
|
+
this.pos++;
|
|
4119
|
+
return -this.num(this.unary(), "the operand of -");
|
|
4120
|
+
}
|
|
4121
|
+
if (this.peekOp("!")) {
|
|
4122
|
+
this.pos++;
|
|
4123
|
+
return !truthOf(this.unary());
|
|
4124
|
+
}
|
|
4125
|
+
return this.primary();
|
|
4126
|
+
}
|
|
4127
|
+
primary() {
|
|
4128
|
+
const token = this.tokens[this.pos++];
|
|
4129
|
+
switch (token.kind) {
|
|
4130
|
+
case "number": return token.value;
|
|
4131
|
+
case "string": return token.value;
|
|
4132
|
+
case "name": return this.name(token.value);
|
|
4133
|
+
case "op":
|
|
4134
|
+
if (token.value === "(") {
|
|
4135
|
+
const value = this.ternary();
|
|
4136
|
+
this.expectOp(")");
|
|
4137
|
+
return value;
|
|
4138
|
+
}
|
|
4139
|
+
return this.fail(`unexpected "${token.value}"`);
|
|
4140
|
+
case "end": return this.fail("missing value");
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
name(name) {
|
|
4144
|
+
if (name === "true") return true;
|
|
4145
|
+
if (name === "false") return false;
|
|
4146
|
+
if (name === "null") return null;
|
|
4147
|
+
if (this.peekOp("(")) {
|
|
4148
|
+
this.pos++;
|
|
4149
|
+
const args = [];
|
|
4150
|
+
if (this.peekOp(")") === void 0) for (;;) {
|
|
4151
|
+
args.push(this.num(this.ternary(), `an argument of ${name}`));
|
|
4152
|
+
if (this.peekOp(",") === void 0) break;
|
|
4153
|
+
this.pos++;
|
|
4154
|
+
}
|
|
4155
|
+
this.expectOp(")");
|
|
4156
|
+
if (this.skipDepth > 0) return null;
|
|
4157
|
+
const fn = Object.prototype.hasOwnProperty.call(MATH, name) ? MATH[name] : void 0;
|
|
4158
|
+
if (!fn) this.fail(`unknown function ${name}`);
|
|
4159
|
+
if (args.length === 0) this.fail(`${name} needs an argument`);
|
|
4160
|
+
return fn(args);
|
|
4161
|
+
}
|
|
4162
|
+
if (this.skipDepth > 0) return null;
|
|
4163
|
+
if (!Object.prototype.hasOwnProperty.call(this.scope, name)) this.fail(`unknown name ${name}; pass it as an arg`);
|
|
4164
|
+
const value = this.scope[name];
|
|
4165
|
+
if (value !== null && typeof value !== "number" && typeof value !== "string" && typeof value !== "boolean") this.fail(`${name} is not a number, text or boolean (${describe(value)})`);
|
|
4166
|
+
return value;
|
|
4167
|
+
}
|
|
4168
|
+
};
|
|
4169
|
+
/**
|
|
4170
|
+
* Evaluates `expression` with the names in `scope`. Gives a number, a
|
|
4171
|
+
* string, a boolean or null. Throws an {@link ExpressionError} for a bad
|
|
4172
|
+
* expression or a missing name.
|
|
4173
|
+
*/
|
|
4174
|
+
function evaluateCalc(expression, scope) {
|
|
4175
|
+
if (typeof expression !== "string" || expression.trim() === "") throw new ExpressionError("calc: expression must be a string");
|
|
4176
|
+
return new CalcParser(tokenize(expression), scope, expression).parse();
|
|
4177
|
+
}
|
|
4178
|
+
/** The `calc` function: the expression is `args.expression`, the rest is the scope. */
|
|
4179
|
+
function calc(args) {
|
|
4180
|
+
const { expression, ...scope } = args;
|
|
4181
|
+
return evaluateCalc(expression, scope);
|
|
4182
|
+
}
|
|
4183
|
+
const WHOLE_EXPRESSION = /^\$\{([^}]*)\}$/;
|
|
4184
|
+
/**
|
|
4185
|
+
* Fills `item` for one iteration: a string that is one `${...}` gives the
|
|
4186
|
+
* typed value of the expression, a string with `${...}` inside it is
|
|
4187
|
+
* interpolated as text, and objects and arrays are walked.
|
|
4188
|
+
*/
|
|
4189
|
+
function fillItem(item, scope) {
|
|
4190
|
+
if (typeof item === "string") {
|
|
4191
|
+
const whole = WHOLE_EXPRESSION.exec(item);
|
|
4192
|
+
if (whole) return evaluateCalc(whole[1], scope);
|
|
4193
|
+
return item.replace(/\$\{([^}]*)\}/g, (_, expression) => {
|
|
4194
|
+
const value = evaluateCalc(expression, scope);
|
|
4195
|
+
return value === null ? "" : String(value);
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
if (Array.isArray(item)) return item.map((entry) => fillItem(entry, scope));
|
|
4199
|
+
if (typeof item === "object" && item !== null) return Object.fromEntries(Object.entries(item).map(([key, value]) => [key, fillItem(value, scope)]));
|
|
4200
|
+
return item;
|
|
4201
|
+
}
|
|
4202
|
+
/**
|
|
4203
|
+
* The `series` function: `count` copies of `item`, with the loop variable
|
|
4204
|
+
* (`variable`, default "i") counting from `start` (default 1) and the other
|
|
4205
|
+
* args in scope of the `${...}` expressions of the item.
|
|
4206
|
+
*/
|
|
4207
|
+
function series(args) {
|
|
4208
|
+
const { count, item, variable, start, ...scope } = args;
|
|
4209
|
+
const length = numberOf(count, "count");
|
|
4210
|
+
if (!Number.isInteger(length) || length < 0) throw new ExpressionError("series: count must be a whole number");
|
|
4211
|
+
if (length > MAX_SERIES) throw new ExpressionError(`series: count is at most ${MAX_SERIES}`);
|
|
4212
|
+
if (item === void 0) throw new ExpressionError("series: item is required");
|
|
4213
|
+
const name = variable === void 0 ? "i" : variable;
|
|
4214
|
+
if (typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new ExpressionError("series: variable must be a name");
|
|
4215
|
+
const first = start === void 0 ? 1 : numberOf(start, "start");
|
|
4216
|
+
return Array.from({ length }, (_, index) => fillItem(item, {
|
|
4217
|
+
...scope,
|
|
4218
|
+
[name]: first + index
|
|
4219
|
+
}));
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4222
|
+
//#endregion
|
|
4223
|
+
//#region src/v2/generative-ui/resolve.ts
|
|
4224
|
+
BASIC_FUNCTIONS.calc = calc;
|
|
4225
|
+
BASIC_FUNCTIONS.series = series;
|
|
4226
|
+
const NO_DATA = { get: () => void 0 };
|
|
4227
|
+
BASIC_FUNCTIONS.formatString = (args) => resolveDynamicValue({
|
|
4228
|
+
"@call": "formatString",
|
|
4229
|
+
args
|
|
4230
|
+
}, {
|
|
4231
|
+
data: NO_DATA,
|
|
4232
|
+
basePath: void 0
|
|
4233
|
+
}).value;
|
|
4234
|
+
/** Adds one segment to a JSON pointer: ("/items", "0") gives "/items/0". */
|
|
4235
|
+
function joinPath(base, segment) {
|
|
4236
|
+
return base === "/" ? `/${segment}` : `${base}/${segment}`;
|
|
4237
|
+
}
|
|
4238
|
+
/**
|
|
4239
|
+
* The absolute data model path of a binding. A path without a leading "/"
|
|
4240
|
+
* is relative to the current template item, or to the root outside of one.
|
|
4241
|
+
*/
|
|
4242
|
+
function resolvePath(path, basePath) {
|
|
4243
|
+
if (path.startsWith("/")) return path;
|
|
4244
|
+
return joinPath(basePath ?? "/", path);
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* The index of the template item at `basePath`: its last segment.
|
|
4248
|
+
* `undefined` outside of a template.
|
|
4249
|
+
*/
|
|
4250
|
+
function templateIndex(basePath) {
|
|
4251
|
+
if (basePath === void 0) return void 0;
|
|
4252
|
+
const index = Number(basePath.slice(basePath.lastIndexOf("/") + 1));
|
|
4253
|
+
return Number.isInteger(index) ? index : void 0;
|
|
4254
|
+
}
|
|
4255
|
+
/** The value of a ValidationResult where a boolean is expected. */
|
|
4256
|
+
function coerce(value, keep) {
|
|
4257
|
+
return !keep && (0, _copilotkit_shared.isValidationResult)(value) ? value.valid : value;
|
|
4258
|
+
}
|
|
4259
|
+
/**
|
|
4260
|
+
* Resolves `value`. `keepValidation` keeps a ValidationResult as it is; by
|
|
4261
|
+
* default it becomes its `valid` boolean, which is what every prop except
|
|
4262
|
+
* a check condition wants.
|
|
4263
|
+
*/
|
|
4264
|
+
function walk(value, state, keepValidation, scope = "outside") {
|
|
4265
|
+
if (Array.isArray(value)) {
|
|
4266
|
+
let changed = false;
|
|
4267
|
+
const itemScope = scope === "list" ? "check" : "outside";
|
|
4268
|
+
const items = value.map((item) => {
|
|
4269
|
+
const next = walk(item, state, keepValidation, itemScope);
|
|
4270
|
+
if (next !== item) changed = true;
|
|
4271
|
+
return next;
|
|
4272
|
+
});
|
|
4273
|
+
return changed ? items : value;
|
|
4274
|
+
}
|
|
4275
|
+
if (!isRecord(value)) return value;
|
|
4276
|
+
if ((0, _copilotkit_shared.isPathBinding)(value)) {
|
|
4277
|
+
const path = resolvePath(value["@path"], state.context.basePath);
|
|
4278
|
+
state.paths.add(path);
|
|
4279
|
+
return state.context.data.get(path);
|
|
4280
|
+
}
|
|
4281
|
+
if ((0, _copilotkit_shared.isCallBinding)(value)) return call(value, state, keepValidation);
|
|
4282
|
+
if ((0, _copilotkit_shared.isActionBinding)(value)) return value;
|
|
4283
|
+
let changed = false;
|
|
4284
|
+
const entries = Object.entries(value).map(([key, item]) => {
|
|
4285
|
+
const next = walk(item, state, keepValidation || scope === "check" && key === "condition", key === "checks" ? "list" : "outside");
|
|
4286
|
+
if (next !== item) changed = true;
|
|
4287
|
+
return [key, next];
|
|
4288
|
+
});
|
|
4289
|
+
return changed ? Object.fromEntries(entries) : value;
|
|
4290
|
+
}
|
|
4291
|
+
function call(binding, state, keepValidation) {
|
|
4292
|
+
const name = binding["@call"];
|
|
4293
|
+
const rawArgs = binding.args ?? {};
|
|
4294
|
+
if (!isRecord(rawArgs)) throw new ExpressionError(`the args of "${name}" must be an object`);
|
|
4295
|
+
if (name === _copilotkit_shared.A2UI_INDEX_FUNCTION) {
|
|
4296
|
+
const index = templateIndex(state.context.basePath);
|
|
4297
|
+
if (index === void 0) return void 0;
|
|
4298
|
+
const offset = walk(rawArgs.offset, state, false);
|
|
4299
|
+
return index + (typeof offset === "number" ? offset : 0);
|
|
4300
|
+
}
|
|
4301
|
+
if (name === "formatString") {
|
|
4302
|
+
const template = walk(rawArgs.value, state, false);
|
|
4303
|
+
if (typeof template !== "string") throw new ExpressionError("formatString takes a string value");
|
|
4304
|
+
return formatString(template, state);
|
|
4305
|
+
}
|
|
4306
|
+
const fn = Object.prototype.hasOwnProperty.call(BASIC_FUNCTIONS, name) ? BASIC_FUNCTIONS[name] : void 0;
|
|
4307
|
+
if (!fn) throw new ExpressionError(`"${name}" is not a basic function`);
|
|
4308
|
+
if (requiresUserActivation(name)) throw new ExpressionError(`"${name}" is not a value; bind it as the functionCall of an event`);
|
|
4309
|
+
const result = fn(walk(rawArgs, state, false));
|
|
4310
|
+
return givesValidationResult(name) ? coerce(result, keepValidation) : result;
|
|
4311
|
+
}
|
|
4312
|
+
function resolveDynamicValue(value, context, options = {}) {
|
|
4313
|
+
const result = tryResolveDynamicValue(value, context, options);
|
|
4314
|
+
if (!result.ok) throw result.error;
|
|
4315
|
+
return result;
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Like {@link resolveDynamicValue}, but a failure comes back as a result
|
|
4319
|
+
* with the paths read so far, so the caller can keep watching them: a
|
|
4320
|
+
* function that fails on an empty field works once the user fills it.
|
|
4321
|
+
*/
|
|
4322
|
+
function tryResolveDynamicValue(value, context, options = {}) {
|
|
4323
|
+
const state = {
|
|
4324
|
+
context,
|
|
4325
|
+
paths: /* @__PURE__ */ new Set()
|
|
4326
|
+
};
|
|
4327
|
+
try {
|
|
4328
|
+
return {
|
|
4329
|
+
ok: true,
|
|
4330
|
+
value: walk(value, state, options.keepValidation ?? false, options.isChecks ? "list" : "outside"),
|
|
4331
|
+
paths: state.paths
|
|
4332
|
+
};
|
|
4333
|
+
} catch (error) {
|
|
4334
|
+
return {
|
|
4335
|
+
ok: false,
|
|
4336
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
4337
|
+
paths: state.paths
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
/** True when `value` holds a `{"@path"}` or `{"@call"}` at any depth. */
|
|
4342
|
+
function containsDynamicValue(value) {
|
|
4343
|
+
if (Array.isArray(value)) return value.some(containsDynamicValue);
|
|
4344
|
+
if (!isRecord(value)) return false;
|
|
4345
|
+
if ((0, _copilotkit_shared.isPathBinding)(value) || (0, _copilotkit_shared.isCallBinding)(value)) return true;
|
|
4346
|
+
if ((0, _copilotkit_shared.isActionBinding)(value)) return false;
|
|
4347
|
+
return Object.values(value).some(containsDynamicValue);
|
|
4348
|
+
}
|
|
4349
|
+
/**
|
|
4350
|
+
* The `${...}` grammar of `formatString`:
|
|
4351
|
+
*
|
|
4352
|
+
* expression := path | literal | call
|
|
4353
|
+
* path := "/" segments | identifier ("/" segments)*
|
|
4354
|
+
* literal := 'text' | "text" | number | true | false | null
|
|
4355
|
+
* call := identifier "(" [ name ":" expression ("," name ":" expression)* ] ")"
|
|
4356
|
+
*
|
|
4357
|
+
* A `\${` in the template is a literal `${`.
|
|
4358
|
+
*/
|
|
4359
|
+
function formatString(template, state) {
|
|
4360
|
+
let out = "";
|
|
4361
|
+
let index = 0;
|
|
4362
|
+
while (index < template.length) {
|
|
4363
|
+
const start = template.indexOf("${", index);
|
|
4364
|
+
if (start === -1) {
|
|
4365
|
+
out += template.slice(index);
|
|
4366
|
+
break;
|
|
4367
|
+
}
|
|
4368
|
+
if (start > 0 && template[start - 1] === "\\") {
|
|
4369
|
+
out += `${template.slice(index, start - 1)}\${`;
|
|
4370
|
+
index = start + 2;
|
|
4371
|
+
continue;
|
|
4372
|
+
}
|
|
4373
|
+
out += template.slice(index, start);
|
|
4374
|
+
const end = matchingBrace(template, start + 2);
|
|
4375
|
+
if (end === -1) throw new ExpressionError("formatString: unclosed ${");
|
|
4376
|
+
const value = new ExpressionParser(template.slice(start + 2, end).trim(), state).parse();
|
|
4377
|
+
out += value === void 0 || value === null ? "" : String(value);
|
|
4378
|
+
index = end + 1;
|
|
4379
|
+
}
|
|
4380
|
+
return out;
|
|
4381
|
+
}
|
|
4382
|
+
/** The index of the `}` that closes the block opened at `from`, or -1. */
|
|
4383
|
+
function matchingBrace(template, from) {
|
|
4384
|
+
let depth = 1;
|
|
4385
|
+
let quote;
|
|
4386
|
+
for (let i = from; i < template.length; i++) {
|
|
4387
|
+
const char = template[i];
|
|
4388
|
+
if (quote) {
|
|
4389
|
+
if (char === "\\") i++;
|
|
4390
|
+
else if (char === quote) quote = void 0;
|
|
4391
|
+
continue;
|
|
4392
|
+
}
|
|
4393
|
+
if (char === "'" || char === "\"") quote = char;
|
|
4394
|
+
else if (char === "{") depth++;
|
|
4395
|
+
else if (char === "}" && --depth === 0) return i;
|
|
4396
|
+
}
|
|
4397
|
+
return -1;
|
|
4398
|
+
}
|
|
4399
|
+
const IDENTIFIER = /@?[A-Za-z_][A-Za-z0-9_-]*/y;
|
|
4400
|
+
const NUMBER = /-?\d+(?:\.\d+)?/y;
|
|
4401
|
+
const PATH_SEGMENT = /[^\s,():'"]+/y;
|
|
4402
|
+
var ExpressionParser = class {
|
|
4403
|
+
constructor(source, state) {
|
|
4404
|
+
this.source = source;
|
|
4405
|
+
this.state = state;
|
|
4406
|
+
this.pos = 0;
|
|
4407
|
+
}
|
|
4408
|
+
parse() {
|
|
4409
|
+
const value = this.expression();
|
|
4410
|
+
this.skipSpace();
|
|
4411
|
+
if (this.pos < this.source.length) throw new ExpressionError(`formatString: unexpected "${this.source.slice(this.pos)}" in \${${this.source}}`);
|
|
4412
|
+
return value;
|
|
4413
|
+
}
|
|
4414
|
+
fail(what) {
|
|
4415
|
+
throw new ExpressionError(`formatString: ${what} in \${${this.source}}`);
|
|
4416
|
+
}
|
|
4417
|
+
skipSpace() {
|
|
4418
|
+
while (this.pos < this.source.length && /\s/.test(this.source[this.pos])) this.pos++;
|
|
4419
|
+
}
|
|
4420
|
+
match(pattern) {
|
|
4421
|
+
pattern.lastIndex = this.pos;
|
|
4422
|
+
const found = pattern.exec(this.source);
|
|
4423
|
+
if (!found) return void 0;
|
|
4424
|
+
this.pos = pattern.lastIndex;
|
|
4425
|
+
return found[0];
|
|
4426
|
+
}
|
|
4427
|
+
expression() {
|
|
4428
|
+
this.skipSpace();
|
|
4429
|
+
const char = this.source[this.pos];
|
|
4430
|
+
if (char === void 0) this.fail("missing expression");
|
|
4431
|
+
if (char === "'" || char === "\"") return this.quoted(char);
|
|
4432
|
+
if (char === "/") return this.path(this.match(PATH_SEGMENT) ?? "/");
|
|
4433
|
+
const number = this.match(NUMBER);
|
|
4434
|
+
if (number !== void 0) return Number(number);
|
|
4435
|
+
const name = this.match(IDENTIFIER);
|
|
4436
|
+
if (name === void 0) this.fail(`unexpected "${char}"`);
|
|
4437
|
+
if (name === "true") return true;
|
|
4438
|
+
if (name === "false") return false;
|
|
4439
|
+
if (name === "null") return null;
|
|
4440
|
+
this.skipSpace();
|
|
4441
|
+
if (this.source[this.pos] === "(") return this.call(name);
|
|
4442
|
+
if (name.startsWith("@")) return call({ "@call": name }, this.state, false);
|
|
4443
|
+
const rest = this.source[this.pos] === "/" ? this.match(PATH_SEGMENT) : "";
|
|
4444
|
+
return this.path(`${name}${rest ?? ""}`);
|
|
4445
|
+
}
|
|
4446
|
+
quoted(quote) {
|
|
4447
|
+
let value = "";
|
|
4448
|
+
this.pos++;
|
|
4449
|
+
while (this.pos < this.source.length) {
|
|
4450
|
+
const char = this.source[this.pos++];
|
|
4451
|
+
if (char === "\\" && this.pos < this.source.length) value += this.source[this.pos++];
|
|
4452
|
+
else if (char === quote) return value;
|
|
4453
|
+
else value += char;
|
|
4454
|
+
}
|
|
4455
|
+
return this.fail("unclosed string");
|
|
4456
|
+
}
|
|
4457
|
+
path(path) {
|
|
4458
|
+
const absolute = resolvePath(path, this.state.context.basePath);
|
|
4459
|
+
this.state.paths.add(absolute);
|
|
4460
|
+
return this.state.context.data.get(absolute);
|
|
4461
|
+
}
|
|
4462
|
+
call(name) {
|
|
4463
|
+
this.pos++;
|
|
4464
|
+
const args = {};
|
|
4465
|
+
this.skipSpace();
|
|
4466
|
+
if (this.source[this.pos] === ")") {
|
|
4467
|
+
this.pos++;
|
|
4468
|
+
return call({
|
|
4469
|
+
"@call": name,
|
|
4470
|
+
args
|
|
4471
|
+
}, this.state, false);
|
|
4472
|
+
}
|
|
4473
|
+
for (;;) {
|
|
4474
|
+
this.skipSpace();
|
|
4475
|
+
const key = this.match(IDENTIFIER);
|
|
4476
|
+
if (key === void 0) this.fail("expected an argument name");
|
|
4477
|
+
this.skipSpace();
|
|
4478
|
+
if (this.source[this.pos] !== ":") this.fail(`expected ":" after ${key}`);
|
|
4479
|
+
this.pos++;
|
|
4480
|
+
args[key] = this.expression();
|
|
4481
|
+
this.skipSpace();
|
|
4482
|
+
const next = this.source[this.pos++];
|
|
4483
|
+
if (next === ")") break;
|
|
4484
|
+
if (next !== ",") this.fail(`expected "," or ")" after ${key}`);
|
|
4485
|
+
}
|
|
4486
|
+
return call({
|
|
4487
|
+
"@call": name,
|
|
4488
|
+
args
|
|
4489
|
+
}, this.state, false);
|
|
4490
|
+
}
|
|
4491
|
+
};
|
|
4492
|
+
|
|
4493
|
+
//#endregion
|
|
4494
|
+
//#region src/v2/generative-ui/schema.ts
|
|
4495
|
+
const ASYNC_ISSUE = { message: "the schema validates asynchronously; component props and state need a schema that validates synchronously" };
|
|
4496
|
+
function toResult(result) {
|
|
4497
|
+
return result.issues ? {
|
|
4498
|
+
success: false,
|
|
4499
|
+
issues: result.issues
|
|
4500
|
+
} : {
|
|
4501
|
+
success: true,
|
|
4502
|
+
data: result.value
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Validates `value` with `schema`. The result comes back at once when the
|
|
4507
|
+
* schema validates synchronously, else as a promise.
|
|
4508
|
+
*/
|
|
4509
|
+
function validateSchema(schema, value) {
|
|
4510
|
+
const result = schema["~standard"].validate(value);
|
|
4511
|
+
return result instanceof Promise ? result.then(toResult) : toResult(result);
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Validates `value` synchronously, for code that runs during render. A
|
|
4515
|
+
* schema that validates asynchronously fails with an issue that says so.
|
|
4516
|
+
*/
|
|
4517
|
+
function validateSchemaSync(schema, value) {
|
|
4518
|
+
const result = validateSchema(schema, value);
|
|
4519
|
+
if (!(result instanceof Promise)) return result;
|
|
4520
|
+
result.catch(() => {});
|
|
4521
|
+
return {
|
|
4522
|
+
success: false,
|
|
4523
|
+
issues: [ASYNC_ISSUE]
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
4526
|
+
/** True for the issue that `validateSchemaSync` gives for an async schema. */
|
|
4527
|
+
function isAsyncIssue(issue) {
|
|
4528
|
+
return issue === ASYNC_ISSUE;
|
|
4529
|
+
}
|
|
4530
|
+
/** The first key of an issue path: the prop the issue is about. */
|
|
4531
|
+
function issueKey(issue) {
|
|
4532
|
+
const first = issue.path?.[0];
|
|
4533
|
+
const key = typeof first === "object" ? first.key : first;
|
|
4534
|
+
return key === void 0 ? void 0 : String(key);
|
|
4535
|
+
}
|
|
4536
|
+
/**
|
|
4537
|
+
* `props` without the top-level props whose `null` the schema rejected, or
|
|
4538
|
+
* `undefined` when there are none. A model writes `null` for "not set" (an
|
|
4539
|
+
* empty selection, a value it has not filled yet), and A2UI deletes a data
|
|
4540
|
+
* model key that is set to `null`, so a rejected `null` is read as an unset
|
|
4541
|
+
* prop. A prop whose schema accepts `null` keeps it.
|
|
4542
|
+
*/
|
|
4543
|
+
function withoutRejectedNulls(props, issues) {
|
|
4544
|
+
const rejected = /* @__PURE__ */ new Set();
|
|
4545
|
+
for (const issue of issues) {
|
|
4546
|
+
const key = issueKey(issue);
|
|
4547
|
+
if (key !== void 0 && issue.path?.length === 1 && props[key] === null) rejected.add(key);
|
|
4548
|
+
}
|
|
4549
|
+
if (rejected.size === 0) return void 0;
|
|
4550
|
+
return Object.fromEntries(Object.entries(props).filter(([key]) => !rejected.has(key)));
|
|
4551
|
+
}
|
|
4552
|
+
/** `"a.b: message; c: message"`. A path-less issue gets `fallback`. */
|
|
4553
|
+
function formatIssues(issues, fallback = "props") {
|
|
4554
|
+
return issues.map((issue) => {
|
|
4555
|
+
return `${(issue.path ?? []).map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".") || fallback}: ${issue.message}`;
|
|
4556
|
+
}).join("; ");
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4559
|
+
//#endregion
|
|
4560
|
+
//#region src/v2/generative-ui/surface-manager.ts
|
|
4561
|
+
function surfaceKey(threadId, surfaceId) {
|
|
4562
|
+
return `${threadId}::${surfaceId}`;
|
|
4563
|
+
}
|
|
4564
|
+
let creationCounter = 0;
|
|
4565
|
+
const FORBIDDEN_SEGMENTS = new Set([
|
|
4566
|
+
"__proto__",
|
|
4567
|
+
"constructor",
|
|
4568
|
+
"prototype"
|
|
4569
|
+
]);
|
|
4570
|
+
/** "/a/b/" to "/a/b". The root stays "/". */
|
|
4571
|
+
function normalizePath(path) {
|
|
4572
|
+
return (path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path) || "/";
|
|
4573
|
+
}
|
|
4574
|
+
/** True when `path` is `ancestor` or below it. */
|
|
4575
|
+
function isAtOrBelow(path, ancestor) {
|
|
4576
|
+
if (ancestor === "/") return true;
|
|
4577
|
+
return path === ancestor || path.startsWith(`${ancestor}/`);
|
|
4578
|
+
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Rejections as `"operation <index>: <reason>"` lines, cut to the size that
|
|
4581
|
+
* the envelope reader accepts.
|
|
4582
|
+
*/
|
|
4583
|
+
function summarizeRejections(rejections) {
|
|
4584
|
+
return rejections.slice(0, _copilotkit_shared.GENERATIVE_UI_MAX_SUMMARY_REJECTIONS).map(({ index, reason }) => `operation ${index}: ${reason}`.slice(0, _copilotkit_shared.GENERATIVE_UI_MAX_REJECTION_LENGTH));
|
|
4585
|
+
}
|
|
4586
|
+
/**
|
|
4587
|
+
* Framework-agnostic store of generated surfaces. One web_core SurfaceModel per
|
|
4588
|
+
* (threadId, surfaceId), shared by every mount. Applies only newly committed
|
|
4589
|
+
* operations, validates each before applying it, and owns app state and the
|
|
4590
|
+
* data model paths that the user wrote, so agent updates cannot overwrite
|
|
4591
|
+
* them. App state never goes into a surface's data model. Selectors read it
|
|
4592
|
+
* with `getState()`.
|
|
4593
|
+
*/
|
|
4594
|
+
var GenerativeUISurfaceManager = class {
|
|
4595
|
+
constructor(ui, options = {}) {
|
|
4596
|
+
this.ui = ui;
|
|
4597
|
+
this.options = options;
|
|
4598
|
+
this.surfaces = /* @__PURE__ */ new Map();
|
|
4599
|
+
this.bookkeeping = /* @__PURE__ */ new Map();
|
|
4600
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
4601
|
+
this.targets = /* @__PURE__ */ new Map();
|
|
4602
|
+
this.state = {};
|
|
4603
|
+
this.owned = /* @__PURE__ */ new Map();
|
|
4604
|
+
this.deletedKeys = /* @__PURE__ */ new Set();
|
|
4605
|
+
this.linked = /* @__PURE__ */ new Map();
|
|
4606
|
+
this.version = 0;
|
|
4607
|
+
this.subscribe = (listener) => {
|
|
4608
|
+
this.listeners.add(listener);
|
|
4609
|
+
return () => {
|
|
4610
|
+
this.listeners.delete(listener);
|
|
4611
|
+
};
|
|
4612
|
+
};
|
|
4613
|
+
this.getVersion = () => this.version;
|
|
4614
|
+
this.getState = () => this.state;
|
|
4615
|
+
this.webCoreCatalog = new _a2ui_web_core_v0_9.Catalog(ui.id, [...ui.components.keys()].map((name) => ({
|
|
4616
|
+
name,
|
|
4617
|
+
schema: zod.z.any()
|
|
4618
|
+
})), []);
|
|
4619
|
+
}
|
|
4620
|
+
notify() {
|
|
4621
|
+
this.version++;
|
|
4622
|
+
for (const listener of [...this.listeners]) listener();
|
|
4623
|
+
}
|
|
4624
|
+
setActiveThread(threadId) {
|
|
4625
|
+
if (threadId === this.activeThreadId) return;
|
|
4626
|
+
this.activeThreadId = threadId;
|
|
4627
|
+
this.notify();
|
|
4628
|
+
}
|
|
4629
|
+
getActiveThread() {
|
|
4630
|
+
return this.activeThreadId;
|
|
4631
|
+
}
|
|
4632
|
+
ingest(threadId, agentId, content) {
|
|
4633
|
+
const key = surfaceKey(threadId, content.surfaceId);
|
|
4634
|
+
const operations = content.operations ?? [];
|
|
4635
|
+
let surface = this.surfaces.get(key);
|
|
4636
|
+
let book = this.bookkeeping.get(key);
|
|
4637
|
+
let changed = false;
|
|
4638
|
+
if (surface && book && !this.extendsApplied(book, operations)) {
|
|
4639
|
+
this.disposeSurface(key);
|
|
4640
|
+
surface = void 0;
|
|
4641
|
+
book = void 0;
|
|
4642
|
+
}
|
|
4643
|
+
if (!surface || !book) {
|
|
4644
|
+
surface = this.createSurface(key, threadId, agentId, content);
|
|
4645
|
+
this.restoreOwnedValues(surface);
|
|
4646
|
+
book = {
|
|
4647
|
+
appliedCount: 0,
|
|
4648
|
+
lastFingerprint: void 0
|
|
4649
|
+
};
|
|
4650
|
+
this.surfaces.set(key, surface);
|
|
4651
|
+
this.bookkeeping.set(key, book);
|
|
4652
|
+
changed = true;
|
|
4653
|
+
}
|
|
4654
|
+
const serverRejected = content.rejected ?? [];
|
|
4655
|
+
if (surface.clientRejected.length > 0 && (surface.status !== "streaming" && content.status === "streaming" || serverRejected.length < surface.serverRejected.length)) {
|
|
4656
|
+
surface.clientRejected = [];
|
|
4657
|
+
changed = true;
|
|
4658
|
+
}
|
|
4659
|
+
if (operations.length > 0) {
|
|
4660
|
+
const lastFingerprint = JSON.stringify(operations[operations.length - 1]);
|
|
4661
|
+
const isReplay = operations.some((op) => (0, _copilotkit_shared.getOperationKind)(op) === "deleteSurface");
|
|
4662
|
+
for (let index = book.appliedCount; index < operations.length; index++) {
|
|
4663
|
+
this.applyOperation(surface, operations[index], {
|
|
4664
|
+
index,
|
|
4665
|
+
isReplay
|
|
4666
|
+
});
|
|
4667
|
+
changed = true;
|
|
4668
|
+
}
|
|
4669
|
+
book.appliedCount = operations.length;
|
|
4670
|
+
surface.operations = operations;
|
|
4671
|
+
book.lastFingerprint = lastFingerprint;
|
|
4672
|
+
}
|
|
4673
|
+
if (surface.status !== content.status || surface.target !== content.target || surface.serverRejected.length !== serverRejected.length) {
|
|
4674
|
+
surface.status = content.status;
|
|
4675
|
+
surface.target = content.target;
|
|
4676
|
+
surface.serverRejected = serverRejected;
|
|
4677
|
+
changed = true;
|
|
4678
|
+
}
|
|
4679
|
+
if (changed) this.notify();
|
|
4680
|
+
}
|
|
4681
|
+
/** The surface of a `key` (see `surfaceKey`), unless it is deleted. */
|
|
4682
|
+
getSurfaceByKey(key) {
|
|
4683
|
+
const surface = this.surfaces.get(key);
|
|
4684
|
+
return surface && !surface.deleted ? surface : void 0;
|
|
4685
|
+
}
|
|
4686
|
+
getSurface(threadId, surfaceId) {
|
|
4687
|
+
const surface = this.surfaces.get(surfaceKey(threadId, surfaceId));
|
|
4688
|
+
return surface && !surface.deleted ? surface : void 0;
|
|
4689
|
+
}
|
|
4690
|
+
getSurfaceForTarget(target, threadId = this.activeThreadId) {
|
|
4691
|
+
if (!threadId) return void 0;
|
|
4692
|
+
let latest;
|
|
4693
|
+
for (const surface of this.surfaces.values()) {
|
|
4694
|
+
if (surface.threadId !== threadId || surface.target !== target || surface.deleted) continue;
|
|
4695
|
+
if (!latest || surface.createdAt > latest.createdAt) latest = surface;
|
|
4696
|
+
}
|
|
4697
|
+
return latest;
|
|
4698
|
+
}
|
|
4699
|
+
/**
|
|
4700
|
+
* A surface whose first createSurface was rejected, so the chat can show
|
|
4701
|
+
* the error where the surface would be. `undefined` for any other surface.
|
|
4702
|
+
*/
|
|
4703
|
+
getFailedSurface(threadId, surfaceId) {
|
|
4704
|
+
const surface = this.surfaces.get(surfaceKey(threadId, surfaceId));
|
|
4705
|
+
return surface?.failed ? surface : void 0;
|
|
4706
|
+
}
|
|
4707
|
+
/**
|
|
4708
|
+
* The surfaces of a thread, for the model. A surface with user-owned
|
|
4709
|
+
* values lists them in `dataModel`, so the model can read what the user
|
|
4710
|
+
* entered. A surface with dropped operations lists them in `rejected`, so
|
|
4711
|
+
* the model can fix them on the next run. A deleted surface, or one that
|
|
4712
|
+
* was never created, has a `status`, so the model knows that the id is
|
|
4713
|
+
* not a live surface.
|
|
4714
|
+
*/
|
|
4715
|
+
describeSurfaces(threadId) {
|
|
4716
|
+
const described = {};
|
|
4717
|
+
for (const surface of this.surfaces.values()) {
|
|
4718
|
+
if (surface.threadId !== threadId) continue;
|
|
4719
|
+
const owned = this.owned.get(surface.key);
|
|
4720
|
+
const rejections = [...surface.serverRejected, ...surface.clientRejected];
|
|
4721
|
+
const status = this.deletedKeys.has(surface.key) ? "deleted" : surface.failed ? "failed" : void 0;
|
|
4722
|
+
described[surface.surfaceId] = {
|
|
4723
|
+
target: surface.target,
|
|
4724
|
+
...owned && owned.size > 0 ? { dataModel: Object.fromEntries(owned) } : {},
|
|
4725
|
+
...rejections.length > 0 && status !== "deleted" ? { rejected: summarizeRejections(rejections) } : {},
|
|
4726
|
+
...status ? { status } : {}
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
return described;
|
|
4730
|
+
}
|
|
4731
|
+
/** Every surface that is not deleted, oldest first, for the inspector. */
|
|
4732
|
+
debugSurfaces() {
|
|
4733
|
+
return [...this.surfaces.values()].filter((surface) => !surface.deleted).sort((a, b) => a.createdAt - b.createdAt).map((surface) => ({
|
|
4734
|
+
surfaceId: surface.surfaceId,
|
|
4735
|
+
threadId: surface.threadId,
|
|
4736
|
+
agentId: surface.agentId,
|
|
4737
|
+
target: surface.target,
|
|
4738
|
+
status: surface.status,
|
|
4739
|
+
operations: surface.operations,
|
|
4740
|
+
dataModel: surface.model.dataModel.get("/"),
|
|
4741
|
+
userValues: Object.fromEntries(this.owned.get(surface.key) ?? []),
|
|
4742
|
+
rejected: [...surface.serverRejected.map((r) => ({
|
|
4743
|
+
...r,
|
|
4744
|
+
source: "runtime"
|
|
4745
|
+
})), ...surface.clientRejected.map((r) => ({
|
|
4746
|
+
...r,
|
|
4747
|
+
source: "browser"
|
|
4748
|
+
}))]
|
|
4749
|
+
}));
|
|
4750
|
+
}
|
|
4751
|
+
/**
|
|
4752
|
+
* Marks a target as mounted. Returns the function that unmounts it. Calling
|
|
4753
|
+
* that function more than once has no effect.
|
|
4754
|
+
*
|
|
4755
|
+
* @param id - The target id.
|
|
4756
|
+
* @param options - The description and the allowed components of this mount.
|
|
4757
|
+
*/
|
|
4758
|
+
registerTarget(id, options = {}) {
|
|
4759
|
+
const registration = { ...options };
|
|
4760
|
+
const registrations = this.targets.get(id) ?? /* @__PURE__ */ new Set();
|
|
4761
|
+
registrations.add(registration);
|
|
4762
|
+
this.targets.set(id, registrations);
|
|
4763
|
+
this.notify();
|
|
4764
|
+
let released = false;
|
|
4765
|
+
return () => {
|
|
4766
|
+
if (released) return;
|
|
4767
|
+
released = true;
|
|
4768
|
+
registrations.delete(registration);
|
|
4769
|
+
if (registrations.size === 0) this.targets.delete(id);
|
|
4770
|
+
this.notify();
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
/**
|
|
4774
|
+
* The mounted targets. Each has the description and the component list of
|
|
4775
|
+
* its earliest mount that has one.
|
|
4776
|
+
*/
|
|
4777
|
+
getMountedTargets() {
|
|
4778
|
+
const mounted = [];
|
|
4779
|
+
for (const [id, registrations] of this.targets) {
|
|
4780
|
+
const description = [...registrations].find((registration) => registration.description !== void 0)?.description;
|
|
4781
|
+
const components = this.targetComponents(id);
|
|
4782
|
+
mounted.push({
|
|
4783
|
+
id,
|
|
4784
|
+
...description === void 0 ? {} : { description },
|
|
4785
|
+
...components === void 0 ? {} : { components: [...components] }
|
|
4786
|
+
});
|
|
4787
|
+
}
|
|
4788
|
+
return mounted;
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4791
|
+
* The component list of the earliest mount of `id` that has one.
|
|
4792
|
+
* `undefined` when no mount of `id` limits its components.
|
|
4793
|
+
*/
|
|
4794
|
+
targetComponents(id) {
|
|
4795
|
+
return [...this.targets.get(id) ?? /* @__PURE__ */ new Set()].find((registration) => registration.components !== void 0)?.components;
|
|
4796
|
+
}
|
|
4797
|
+
/** The components that `validateOperation` allows on a surface. */
|
|
4798
|
+
allowedComponentsFor(surface) {
|
|
4799
|
+
const components = this.targetComponents(surface.target);
|
|
4800
|
+
return components === void 0 ? void 0 : {
|
|
4801
|
+
target: surface.target,
|
|
4802
|
+
names: new Set(components)
|
|
4803
|
+
};
|
|
4804
|
+
}
|
|
4805
|
+
/** Sets one state key. Notifies only when the value changed. */
|
|
4806
|
+
setState(key, value) {
|
|
4807
|
+
if (Object.hasOwn(this.state, key) && Object.is(this.state[key], value)) return;
|
|
4808
|
+
this.state = {
|
|
4809
|
+
...this.state,
|
|
4810
|
+
[key]: value
|
|
4811
|
+
};
|
|
4812
|
+
this.notify();
|
|
4813
|
+
}
|
|
4814
|
+
/** Removes one state key. Notifies only when the key was set. */
|
|
4815
|
+
removeState(key) {
|
|
4816
|
+
if (!Object.hasOwn(this.state, key)) return;
|
|
4817
|
+
const { [key]: _removed, ...rest } = this.state;
|
|
4818
|
+
this.state = rest;
|
|
4819
|
+
this.notify();
|
|
4820
|
+
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Makes the surfaces of `surfaceIds` share their data model: a user write
|
|
4823
|
+
* to one of them goes to all of them (see `setOwnedValue`). The markdown
|
|
4824
|
+
* transport links the parts of a chat surface that the prose lift split.
|
|
4825
|
+
* A later call for one of the surfaces replaces its group.
|
|
4826
|
+
*
|
|
4827
|
+
* @param threadId - The thread of the surfaces.
|
|
4828
|
+
* @param surfaceIds - The surface ids of the group. One id clears a link.
|
|
4829
|
+
*/
|
|
4830
|
+
linkSurfaces(threadId, surfaceIds) {
|
|
4831
|
+
const keys = new Set(surfaceIds.map((id) => surfaceKey(threadId, id)));
|
|
4832
|
+
for (const key of keys) if (keys.size > 1) this.linked.set(key, keys);
|
|
4833
|
+
else this.linked.delete(key);
|
|
4834
|
+
}
|
|
4835
|
+
/**
|
|
4836
|
+
* Writes a user value into the data model of a surface, and marks `path`
|
|
4837
|
+
* as user-owned. Then agent writes to that path, or below it, are ignored.
|
|
4838
|
+
* An agent write above it keeps the user value. Ownership ends when the
|
|
4839
|
+
* surface is deleted. Does nothing for an unknown or deleted surface. The
|
|
4840
|
+
* surfaces linked to it (see `linkSurfaces`) get the same write.
|
|
4841
|
+
*
|
|
4842
|
+
* @param key - The `key` of the surface.
|
|
4843
|
+
* @param path - An absolute JSON pointer, for example "/form/email".
|
|
4844
|
+
* @param value - The new value. It is copied.
|
|
4845
|
+
* @throws Error when a path segment is "__proto__", "constructor" or
|
|
4846
|
+
* "prototype", or when the path does not fit the data (for example, a
|
|
4847
|
+
* segment above it holds a string).
|
|
4848
|
+
*/
|
|
4849
|
+
setOwnedValue(key, path, value) {
|
|
4850
|
+
const normalized = normalizePath(path);
|
|
4851
|
+
const forbidden = normalized.split("/").find((segment) => FORBIDDEN_SEGMENTS.has(segment));
|
|
4852
|
+
if (forbidden !== void 0) throw new Error(`[generativeUI] path segment "${forbidden}" is not allowed in "${path}".`);
|
|
4853
|
+
for (const target of this.linked.get(key) ?? [key]) this.writeOwnedValue(target, normalized, value);
|
|
4854
|
+
}
|
|
4855
|
+
writeOwnedValue(key, path, value) {
|
|
4856
|
+
const surface = this.surfaces.get(key);
|
|
4857
|
+
if (!surface || surface.deleted) return;
|
|
4858
|
+
surface.model.dataModel.set(path, structuredClone(value));
|
|
4859
|
+
const owned = this.owned.get(key) ?? /* @__PURE__ */ new Map();
|
|
4860
|
+
owned.set(path, structuredClone(value));
|
|
4861
|
+
this.owned.set(key, owned);
|
|
4862
|
+
}
|
|
4863
|
+
/**
|
|
4864
|
+
* Writes the user-owned values into the new, empty data model of a rebuilt
|
|
4865
|
+
* surface. The agent operations are applied after it.
|
|
4866
|
+
*/
|
|
4867
|
+
restoreOwnedValues(surface) {
|
|
4868
|
+
const owned = this.owned.get(surface.key) ?? /* @__PURE__ */ new Map();
|
|
4869
|
+
for (const [path, value] of owned) surface.model.dataModel.set(path, structuredClone(value));
|
|
4870
|
+
}
|
|
4871
|
+
/**
|
|
4872
|
+
* The value that an agent write puts at `path`, with the user-owned values
|
|
4873
|
+
* below `path` written back into it. `undefined` when `path` is user-owned
|
|
4874
|
+
* or below a user-owned path, so the agent cannot write it. Throws when a
|
|
4875
|
+
* user value does not fit the new value.
|
|
4876
|
+
*/
|
|
4877
|
+
mergeOwnedValues(key, path, value) {
|
|
4878
|
+
const merged = { value: structuredClone(value) };
|
|
4879
|
+
const owned = this.owned.get(key);
|
|
4880
|
+
if (!owned) return merged;
|
|
4881
|
+
const ownedPaths = [...owned.keys()];
|
|
4882
|
+
if (ownedPaths.some((ownedPath) => isAtOrBelow(path, ownedPath))) return void 0;
|
|
4883
|
+
const below = ownedPaths.filter((ownedPath) => isAtOrBelow(ownedPath, path));
|
|
4884
|
+
if (below.length === 0) return merged;
|
|
4885
|
+
const scratch = new _a2ui_web_core_v0_9.DataModel({ value: merged.value });
|
|
4886
|
+
const prefix = path === "/" ? "" : path;
|
|
4887
|
+
for (const ownedPath of below) {
|
|
4888
|
+
const relative = ownedPath.slice(prefix.length);
|
|
4889
|
+
scratch.set(`/value${relative}`, structuredClone(owned.get(ownedPath)));
|
|
4890
|
+
}
|
|
4891
|
+
return { value: scratch.get("/value") };
|
|
4892
|
+
}
|
|
4893
|
+
extendsApplied(book, operations) {
|
|
4894
|
+
if (operations.length < book.appliedCount) return false;
|
|
4895
|
+
if (book.appliedCount === 0) return true;
|
|
4896
|
+
return JSON.stringify(operations[book.appliedCount - 1]) === book.lastFingerprint;
|
|
4897
|
+
}
|
|
4898
|
+
createSurface(key, threadId, agentId, content) {
|
|
4899
|
+
return {
|
|
4900
|
+
key,
|
|
4901
|
+
threadId,
|
|
4902
|
+
surfaceId: content.surfaceId,
|
|
4903
|
+
agentId,
|
|
4904
|
+
model: new _a2ui_web_core_v0_9.SurfaceModel(content.surfaceId, this.webCoreCatalog),
|
|
4905
|
+
createdAt: ++creationCounter,
|
|
4906
|
+
target: content.target,
|
|
4907
|
+
status: content.status,
|
|
4908
|
+
deleted: false,
|
|
4909
|
+
failed: false,
|
|
4910
|
+
serverRejected: content.rejected ?? [],
|
|
4911
|
+
clientRejected: [],
|
|
4912
|
+
operations: []
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
disposeSurface(key) {
|
|
4916
|
+
this.surfaces.get(key)?.model.dispose();
|
|
4917
|
+
this.surfaces.delete(key);
|
|
4918
|
+
this.bookkeeping.delete(key);
|
|
4919
|
+
}
|
|
4920
|
+
/**
|
|
4921
|
+
* Marks a surface deleted and frees its components and data. The record
|
|
4922
|
+
* stays, so the model is still told about the id and the bookkeeping does
|
|
4923
|
+
* not rebuild the surface for content that was already applied.
|
|
4924
|
+
*/
|
|
4925
|
+
closeSurface(surface) {
|
|
4926
|
+
surface.deleted = true;
|
|
4927
|
+
surface.model.dispose();
|
|
4928
|
+
surface.model = new _a2ui_web_core_v0_9.SurfaceModel(surface.surfaceId, this.webCoreCatalog);
|
|
4929
|
+
this.owned.delete(surface.key);
|
|
4930
|
+
this.linked.delete(surface.key);
|
|
4931
|
+
}
|
|
4932
|
+
/** The surfaces of a thread that a part of `surfaceId` reads, for tests. */
|
|
4933
|
+
getLinkedSurfaceIds(threadId, surfaceId) {
|
|
4934
|
+
const keys = this.linked.get(surfaceKey(threadId, surfaceId));
|
|
4935
|
+
if (!keys) return [surfaceId];
|
|
4936
|
+
const prefix = `${threadId}::`;
|
|
4937
|
+
return [...keys].map((key) => key.slice(prefix.length));
|
|
4938
|
+
}
|
|
4939
|
+
/**
|
|
4940
|
+
* Drops the node keys that the app sets: bound props and matched events.
|
|
4941
|
+
* The model does not see them, so a model value for one is ignored, not
|
|
4942
|
+
* rejected. This runs before validation, because validation rejects keys
|
|
4943
|
+
* that are not in the manifest. It never changes the given operation.
|
|
4944
|
+
*/
|
|
4945
|
+
dropAppSetKeys(op) {
|
|
4946
|
+
const kind = (0, _copilotkit_shared.getOperationKind)(op);
|
|
4947
|
+
if (kind !== "updateComponents" && kind !== "createSurface") return op;
|
|
4948
|
+
const record = op;
|
|
4949
|
+
const payload = record[kind];
|
|
4950
|
+
const nodes = payload?.components;
|
|
4951
|
+
if (!Array.isArray(nodes)) return op;
|
|
4952
|
+
return {
|
|
4953
|
+
...record,
|
|
4954
|
+
[kind]: {
|
|
4955
|
+
...payload,
|
|
4956
|
+
components: nodes.map((node) => this.dropAppSetNodeKeys(node))
|
|
4957
|
+
}
|
|
4958
|
+
};
|
|
4959
|
+
}
|
|
4960
|
+
dropAppSetNodeKeys(node) {
|
|
4961
|
+
if (typeof node !== "object" || node === null || !("component" in node)) return node;
|
|
4962
|
+
const { component } = node;
|
|
4963
|
+
if (typeof component !== "string") return node;
|
|
4964
|
+
const definition = this.ui.components.get(component);
|
|
4965
|
+
if (!definition) return node;
|
|
4966
|
+
return Object.fromEntries(Object.entries(node).filter(([key]) => !Object.hasOwn(definition.selectors, key) && !Object.hasOwn(definition.eventFunctions, key)));
|
|
4967
|
+
}
|
|
4968
|
+
/**
|
|
4969
|
+
* The surfaces that `validateOperation` gets for one operation of the
|
|
4970
|
+
* content of `surface`.
|
|
4971
|
+
*
|
|
4972
|
+
* - `knownSurfaces`: the surface, when it exists before the operation. It
|
|
4973
|
+
* does not exist before its first createSurface, or after a delete.
|
|
4974
|
+
* - `createdSurfaces`: the surface, when a createSurface at this position
|
|
4975
|
+
* uses its id again. That is the case after the first operation, and for
|
|
4976
|
+
* a surface that was deleted before, unless the content replays that
|
|
4977
|
+
* delete.
|
|
4978
|
+
*/
|
|
4979
|
+
surfacesFor(surface, kind, position) {
|
|
4980
|
+
const isFirstCreate = kind === "createSurface" && position.index === 0;
|
|
4981
|
+
const exists = !surface.deleted && !isFirstCreate;
|
|
4982
|
+
const isRecreate = this.deletedKeys.has(surface.key) && !position.isReplay;
|
|
4983
|
+
const isUsedAgain = position.index > 0 || isRecreate;
|
|
4984
|
+
return {
|
|
4985
|
+
knownSurfaces: new Set(exists ? [surface.surfaceId] : []),
|
|
4986
|
+
createdSurfaces: new Set(isUsedAgain ? [surface.surfaceId] : [])
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
applyOperation(surface, received, position) {
|
|
4990
|
+
const { index } = position;
|
|
4991
|
+
const kind = (0, _copilotkit_shared.getOperationKind)(received);
|
|
4992
|
+
if (kind === "callRendererFunction") return;
|
|
4993
|
+
const reject = (reason) => {
|
|
4994
|
+
if (kind === "createSurface" && index === 0) {
|
|
4995
|
+
this.closeSurface(surface);
|
|
4996
|
+
surface.failed = true;
|
|
4997
|
+
}
|
|
4998
|
+
surface.clientRejected.push({
|
|
4999
|
+
index,
|
|
5000
|
+
reason
|
|
5001
|
+
});
|
|
5002
|
+
this.options.onOperationRejected?.({
|
|
5003
|
+
surfaceId: surface.surfaceId,
|
|
5004
|
+
index,
|
|
5005
|
+
reason
|
|
5006
|
+
});
|
|
5007
|
+
};
|
|
5008
|
+
const targetId = (0, _copilotkit_shared.getOperationSurfaceId)(received);
|
|
5009
|
+
if (targetId !== void 0 && targetId !== surface.surfaceId) return reject(`operation is for surface "${targetId}", not "${surface.surfaceId}"`);
|
|
5010
|
+
const op = this.dropAppSetKeys(received);
|
|
5011
|
+
const validation = (0, _copilotkit_shared.validateOperation)(op, {
|
|
5012
|
+
manifest: {
|
|
5013
|
+
catalogId: this.ui.id,
|
|
5014
|
+
catalogIds: catalogIdsOf(this.ui),
|
|
5015
|
+
components: this.ui.manifestComponents
|
|
5016
|
+
},
|
|
5017
|
+
...this.surfacesFor(surface, kind, position),
|
|
5018
|
+
allowedComponents: this.allowedComponentsFor(surface)
|
|
5019
|
+
});
|
|
5020
|
+
if (!validation.ok) return reject(validation.reason);
|
|
5021
|
+
const typed = op;
|
|
5022
|
+
if ("createSurface" in typed) {
|
|
5023
|
+
const { dataModel, components = [] } = typed.createSurface;
|
|
5024
|
+
const problem = this.nodesProblem(components);
|
|
5025
|
+
if (problem) return reject(problem);
|
|
5026
|
+
if (dataModel) {
|
|
5027
|
+
const failure = this.writeAgentData(surface, "/", dataModel);
|
|
5028
|
+
if (failure) return reject(failure);
|
|
5029
|
+
}
|
|
5030
|
+
for (const node of components) this.upsertNode(surface.model, node);
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
if ("updateComponents" in typed) {
|
|
5034
|
+
const nodes = typed.updateComponents.components;
|
|
5035
|
+
const problem = this.nodesProblem(nodes);
|
|
5036
|
+
if (problem) return reject(problem);
|
|
5037
|
+
for (const node of nodes) this.upsertNode(surface.model, node);
|
|
5038
|
+
return;
|
|
5039
|
+
}
|
|
5040
|
+
if ("updateDataModel" in typed) {
|
|
5041
|
+
const { path = "/", value } = typed.updateDataModel;
|
|
5042
|
+
const written = value === null ? void 0 : value;
|
|
5043
|
+
const failure = this.writeAgentData(surface, normalizePath(path), written);
|
|
5044
|
+
if (failure) return reject(failure);
|
|
5045
|
+
return;
|
|
5046
|
+
}
|
|
5047
|
+
if ("deleteSurface" in typed) {
|
|
5048
|
+
this.closeSurface(surface);
|
|
5049
|
+
this.deletedKeys.add(surface.key);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
/** The first node with props of the wrong type, as a reason. */
|
|
5053
|
+
nodesProblem(nodes) {
|
|
5054
|
+
for (const node of nodes) {
|
|
5055
|
+
const problem = this.checkNodeProps(node);
|
|
5056
|
+
if (problem) return `component "${node.id}": ${problem}`;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
/**
|
|
5060
|
+
* Writes an agent value at `path` and keeps the user-owned values. A write
|
|
5061
|
+
* to a user-owned path is ignored, not rejected. Gives the reason when the
|
|
5062
|
+
* value does not fit the data, else `undefined`.
|
|
5063
|
+
*/
|
|
5064
|
+
writeAgentData(surface, path, value) {
|
|
5065
|
+
try {
|
|
5066
|
+
const merged = this.mergeOwnedValues(surface.key, path, value);
|
|
5067
|
+
if (merged) surface.model.dataModel.set(path, merged.value);
|
|
5068
|
+
return;
|
|
5069
|
+
} catch (error) {
|
|
5070
|
+
return error instanceof Error ? error.message : String(error);
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
checkNodeProps(node) {
|
|
5074
|
+
const definition = this.ui.components.get(node.component);
|
|
5075
|
+
if (!definition) return `unknown component "${node.component}"`;
|
|
5076
|
+
const { id: _id, component: _component, ...rest } = node;
|
|
5077
|
+
const dataProps = Object.fromEntries(Object.entries(rest).filter(([key]) => !definition.events?.[key] && !definition.slots?.[key] && !_copilotkit_shared.A2UI_COMPONENT_ENVELOPE_KEYS.has(key)));
|
|
5078
|
+
if (!definition.props) return void 0;
|
|
5079
|
+
const unknownNow = new Set(Object.keys(definition.selectors));
|
|
5080
|
+
const literals = {};
|
|
5081
|
+
for (const [key, value] of Object.entries(dataProps)) if (containsDynamicValue(value)) unknownNow.add(key);
|
|
5082
|
+
else literals[key] = value;
|
|
5083
|
+
let result = validateSchemaSync(definition.props, literals);
|
|
5084
|
+
if (!result.success) {
|
|
5085
|
+
const unset = withoutRejectedNulls(literals, result.issues);
|
|
5086
|
+
if (unset) result = validateSchemaSync(definition.props, unset);
|
|
5087
|
+
}
|
|
5088
|
+
if (result.success) return void 0;
|
|
5089
|
+
const issues = result.issues.filter((issue) => {
|
|
5090
|
+
const key = issueKey(issue);
|
|
5091
|
+
return key === void 0 ? isAsyncIssue(issue) : !unknownNow.has(key);
|
|
5092
|
+
});
|
|
5093
|
+
return issues.length > 0 ? formatIssues(issues) : void 0;
|
|
5094
|
+
}
|
|
5095
|
+
upsertNode(model, node) {
|
|
5096
|
+
const { id, component, ...rest } = node;
|
|
5097
|
+
const properties = structuredClone(rest);
|
|
5098
|
+
const existing = model.componentsModel.get(id);
|
|
5099
|
+
if (existing && existing.type === component) {
|
|
5100
|
+
existing.properties = properties;
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
if (existing) model.componentsModel.removeComponent(id);
|
|
5104
|
+
model.componentsModel.addComponent(new _a2ui_web_core_v0_9.ComponentModel(id, component, properties));
|
|
5105
|
+
}
|
|
5106
|
+
};
|
|
5107
|
+
|
|
5108
|
+
//#endregion
|
|
5109
|
+
//#region src/v2/generative-ui/use-generative-ui-root.ts
|
|
5110
|
+
const isDevelopment = () => process.env.NODE_ENV !== "production";
|
|
5111
|
+
/** At most this many function follow-up runs for each user turn. */
|
|
5112
|
+
const MAX_FUNCTION_FOLLOW_UPS = 5;
|
|
5113
|
+
function ingestMessages(manager, agent, messages) {
|
|
5114
|
+
if (!agent.threadId) return;
|
|
5115
|
+
for (const message of messages) if (message.role === "activity" && message.activityType === _copilotkit_shared.GENERATIVE_UI_ACTIVITY_TYPE) manager.ingest(agent.threadId, agent.agentId, message.content);
|
|
5116
|
+
}
|
|
5117
|
+
/** What the envelope of a run says, from the provider config. */
|
|
5118
|
+
function envelopeOptionsFor(config) {
|
|
5119
|
+
if (config.transport === "markdown") return {
|
|
5120
|
+
transport: "markdown",
|
|
5121
|
+
includeCatalog: config.includeCatalog !== false,
|
|
5122
|
+
contextSent: config.sendContext !== false
|
|
5123
|
+
};
|
|
5124
|
+
return {
|
|
5125
|
+
transport: "tool",
|
|
5126
|
+
includeCatalog: true,
|
|
5127
|
+
contextSent: false
|
|
5128
|
+
};
|
|
5129
|
+
}
|
|
5130
|
+
/**
|
|
5131
|
+
* The components of a surface as one `updateComponents` operation, so that
|
|
5132
|
+
* the label of the component that sent an event can be found.
|
|
5133
|
+
*/
|
|
5134
|
+
function surfaceComponents(surface) {
|
|
5135
|
+
const components = [...surface.model.componentsModel.entries].map(([id, model]) => ({
|
|
5136
|
+
...model.properties,
|
|
5137
|
+
id,
|
|
5138
|
+
component: model.type
|
|
5139
|
+
}));
|
|
5140
|
+
return [{
|
|
5141
|
+
version: _copilotkit_shared.A2UI_VERSION,
|
|
5142
|
+
updateComponents: {
|
|
5143
|
+
surfaceId: surface.surfaceId,
|
|
5144
|
+
components
|
|
5145
|
+
}
|
|
5146
|
+
}];
|
|
5147
|
+
}
|
|
5148
|
+
/** True when `value` names a `functionCallId`. */
|
|
5149
|
+
function hasFunctionCallId(value) {
|
|
5150
|
+
return typeof value === "object" && value !== null && "functionCallId" in value && typeof value.functionCallId === "string" && value.functionCallId !== "";
|
|
5151
|
+
}
|
|
5152
|
+
/** The `functionCallId` that a developer message answers, if any. */
|
|
5153
|
+
function answeredCallId(content) {
|
|
5154
|
+
if (!content.includes("rendererFunctionResponse")) return void 0;
|
|
5155
|
+
try {
|
|
5156
|
+
const parsed = JSON.parse(content);
|
|
5157
|
+
const response = typeof parsed === "object" && parsed !== null && "rendererFunctionResponse" in parsed ? parsed.rendererFunctionResponse : void 0;
|
|
5158
|
+
return hasFunctionCallId(response) ? response.functionCallId : void 0;
|
|
5159
|
+
} catch {
|
|
5160
|
+
return;
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
/** The function calls that a developer message in `messages` answers. */
|
|
5164
|
+
function answeredCallIds(messages) {
|
|
5165
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5166
|
+
for (const message of messages) {
|
|
5167
|
+
if (message.role !== "developer") continue;
|
|
5168
|
+
const id = answeredCallId(message.content);
|
|
5169
|
+
if (id !== void 0) ids.add(id);
|
|
5170
|
+
}
|
|
5171
|
+
return ids;
|
|
5172
|
+
}
|
|
5173
|
+
/** The id of the last user message. A user turn starts with it. */
|
|
5174
|
+
function lastUserMessageId(messages) {
|
|
5175
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
5176
|
+
const message = messages[index];
|
|
5177
|
+
if (message?.role === "user") return message.id;
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
/** The function calls of the tool transport: its function-call activities. */
|
|
5181
|
+
function activityFunctionCalls(messages) {
|
|
5182
|
+
const requests = [];
|
|
5183
|
+
for (const message of messages) {
|
|
5184
|
+
if (!(message.role === "activity" && message.activityType === _copilotkit_shared.GENERATIVE_UI_FUNCTION_CALL_ACTIVITY_TYPE) || !hasFunctionCallId(message.content)) continue;
|
|
5185
|
+
requests.push({
|
|
5186
|
+
functionCallId: message.content.functionCallId,
|
|
5187
|
+
operation: {
|
|
5188
|
+
version: _copilotkit_shared.A2UI_VERSION,
|
|
5189
|
+
callRendererFunction: message.content
|
|
5190
|
+
}
|
|
5191
|
+
});
|
|
5192
|
+
}
|
|
5193
|
+
return requests;
|
|
5194
|
+
}
|
|
5195
|
+
/** The function calls of the markdown transport: its fence lines. */
|
|
5196
|
+
function markdownFunctionCalls(calls) {
|
|
5197
|
+
const requests = [];
|
|
5198
|
+
for (const { operation } of calls) {
|
|
5199
|
+
const payload = operation.callRendererFunction;
|
|
5200
|
+
if (!hasFunctionCallId(payload)) continue;
|
|
5201
|
+
requests.push({
|
|
5202
|
+
functionCallId: payload.functionCallId,
|
|
5203
|
+
operation
|
|
5204
|
+
});
|
|
5205
|
+
}
|
|
5206
|
+
return requests;
|
|
5207
|
+
}
|
|
5208
|
+
/** The text of a `rendererFunctionResponse` developer message. */
|
|
5209
|
+
function responseContent(response) {
|
|
5210
|
+
return JSON.stringify({
|
|
5211
|
+
version: _copilotkit_shared.A2UI_VERSION,
|
|
5212
|
+
rendererFunctionResponse: response
|
|
5213
|
+
});
|
|
5214
|
+
}
|
|
5215
|
+
function errorAnswer(functionCallId, code, message) {
|
|
5216
|
+
return {
|
|
5217
|
+
content: responseContent({
|
|
5218
|
+
functionCallId,
|
|
5219
|
+
error: {
|
|
5220
|
+
code,
|
|
5221
|
+
message
|
|
5222
|
+
}
|
|
5223
|
+
}),
|
|
5224
|
+
followUp: true
|
|
5225
|
+
};
|
|
5226
|
+
}
|
|
5227
|
+
const overCapAnswer = (functionCallId) => ({
|
|
5228
|
+
content: responseContent({
|
|
5229
|
+
functionCallId,
|
|
5230
|
+
error: {
|
|
5231
|
+
code: "follow_up_limit",
|
|
5232
|
+
message: `The function did not run. A user turn can have at most ${MAX_FUNCTION_FOLLOW_UPS} function follow-up runs. Wait for the next user message.`
|
|
5233
|
+
}
|
|
5234
|
+
}),
|
|
5235
|
+
followUp: false
|
|
5236
|
+
});
|
|
5237
|
+
/**
|
|
5238
|
+
* Runs the functions that the agent calls with `callRendererFunction`, for
|
|
5239
|
+
* both transports, and answers each call.
|
|
5240
|
+
*
|
|
5241
|
+
* - Each `functionCallId` runs once for each thread. A replay, a sync of
|
|
5242
|
+
* the same messages again, or a call that already has an answer message
|
|
5243
|
+
* does not run it again.
|
|
5244
|
+
* - The answer is a hidden developer message with a `rendererFunctionResponse`.
|
|
5245
|
+
* The answers go to the agent only when it is not running. Then one
|
|
5246
|
+
* follow-up run starts for all of them.
|
|
5247
|
+
* - A user turn has at most {@link MAX_FUNCTION_FOLLOW_UPS} follow-up runs.
|
|
5248
|
+
* A call after that does not run. It gets a `follow_up_limit` error and
|
|
5249
|
+
* starts no follow-up run.
|
|
5250
|
+
*/
|
|
5251
|
+
function createFunctionCallRunner(options) {
|
|
5252
|
+
const { copilotkit, dispatcher, manifest } = options;
|
|
5253
|
+
const threads = /* @__PURE__ */ new Map();
|
|
5254
|
+
const threadState = (threadId) => {
|
|
5255
|
+
let state = threads.get(threadId);
|
|
5256
|
+
if (!state) {
|
|
5257
|
+
state = {
|
|
5258
|
+
done: /* @__PURE__ */ new Set(),
|
|
5259
|
+
pending: [],
|
|
5260
|
+
turn: void 0,
|
|
5261
|
+
followUps: 0,
|
|
5262
|
+
flushing: false
|
|
5263
|
+
};
|
|
5264
|
+
threads.set(threadId, state);
|
|
5265
|
+
}
|
|
5266
|
+
return state;
|
|
5267
|
+
};
|
|
5268
|
+
const answer = async (request) => {
|
|
5269
|
+
const { functionCallId } = request;
|
|
5270
|
+
const validation = (0, _copilotkit_shared.validateOperation)(request.operation, {
|
|
5271
|
+
manifest,
|
|
5272
|
+
knownSurfaces: /* @__PURE__ */ new Set()
|
|
5273
|
+
});
|
|
5274
|
+
if (!validation.ok) return errorAnswer(functionCallId, "invalid_arguments", validation.reason);
|
|
5275
|
+
const operation = request.operation;
|
|
5276
|
+
if (!("callRendererFunction" in operation)) return errorAnswer(functionCallId, "invalid_arguments", "the operation is not a callRendererFunction");
|
|
5277
|
+
const { callFunction } = operation.callRendererFunction;
|
|
5278
|
+
const name = callFunction["@call"];
|
|
5279
|
+
const result = await dispatcher.invoke(name, callFunction.args ?? {}, { functionCallId });
|
|
5280
|
+
if (!result.ok) return errorAnswer(functionCallId, result.error.code, result.error.message);
|
|
5281
|
+
try {
|
|
5282
|
+
return {
|
|
5283
|
+
content: responseContent({
|
|
5284
|
+
functionCallId,
|
|
5285
|
+
value: result.result ?? null
|
|
5286
|
+
}),
|
|
5287
|
+
followUp: true
|
|
5288
|
+
};
|
|
5289
|
+
} catch (cause) {
|
|
5290
|
+
return errorAnswer(functionCallId, "handler_failed", `The result of "${name}" is not JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
5291
|
+
}
|
|
5292
|
+
};
|
|
5293
|
+
/**
|
|
5294
|
+
* Adds the pending answers of a thread to the agent, then starts one
|
|
5295
|
+
* follow-up run. It waits while the agent runs: the next read of the
|
|
5296
|
+
* thread, at the end of the run, flushes again.
|
|
5297
|
+
*/
|
|
5298
|
+
const flush = async (agent, threadId, state) => {
|
|
5299
|
+
if (state.flushing) return;
|
|
5300
|
+
state.flushing = true;
|
|
5301
|
+
try {
|
|
5302
|
+
while (state.pending.length > 0) {
|
|
5303
|
+
const batch = [...state.pending];
|
|
5304
|
+
const answers = await Promise.all(batch);
|
|
5305
|
+
await copilotkit.waitForPendingFrameworkUpdates();
|
|
5306
|
+
if (agent.isRunning || agent.threadId !== threadId) return;
|
|
5307
|
+
if (state.pending.length > batch.length) continue;
|
|
5308
|
+
state.pending = [];
|
|
5309
|
+
for (const { content } of answers) agent.addMessage({
|
|
5310
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
5311
|
+
role: "developer",
|
|
5312
|
+
content
|
|
5313
|
+
});
|
|
5314
|
+
if (!answers.some((item) => item.followUp)) continue;
|
|
5315
|
+
state.followUps++;
|
|
5316
|
+
await copilotkit.runAgent({ agent });
|
|
5317
|
+
}
|
|
5318
|
+
} catch (error) {
|
|
5319
|
+
if (isDevelopment()) console.warn("[generativeUI] function follow-up run failed:", error);
|
|
5320
|
+
} finally {
|
|
5321
|
+
state.flushing = false;
|
|
5322
|
+
}
|
|
5323
|
+
};
|
|
5324
|
+
return { run(agent, requests, messages, running) {
|
|
5325
|
+
const { threadId } = agent;
|
|
5326
|
+
if (!threadId) return;
|
|
5327
|
+
const state = threadState(threadId);
|
|
5328
|
+
const turn = lastUserMessageId(messages);
|
|
5329
|
+
if (turn !== state.turn) {
|
|
5330
|
+
state.turn = turn;
|
|
5331
|
+
state.followUps = 0;
|
|
5332
|
+
}
|
|
5333
|
+
const isNew = (request) => !state.done.has(request.functionCallId);
|
|
5334
|
+
const fresh = requests.filter(isNew);
|
|
5335
|
+
const answered = fresh.length > 0 ? answeredCallIds(messages) : /* @__PURE__ */ new Set();
|
|
5336
|
+
for (const request of fresh) {
|
|
5337
|
+
const { functionCallId } = request;
|
|
5338
|
+
if (state.done.has(functionCallId)) continue;
|
|
5339
|
+
state.done.add(functionCallId);
|
|
5340
|
+
if (answered.has(functionCallId)) continue;
|
|
5341
|
+
const isOverCap = state.followUps >= MAX_FUNCTION_FOLLOW_UPS;
|
|
5342
|
+
state.pending.push(isOverCap ? Promise.resolve(overCapAnswer(functionCallId)) : answer(request));
|
|
5343
|
+
}
|
|
5344
|
+
if (!running) flush(agent, threadId, state);
|
|
5345
|
+
} };
|
|
5346
|
+
}
|
|
5347
|
+
/** Provider-side wiring: manager, dispatcher, agent subscriptions, envelope. */
|
|
5348
|
+
function useGenerativeUIRoot(copilotkit, config) {
|
|
5349
|
+
const configRef = (0, react.useRef)(config);
|
|
5350
|
+
configRef.current = config;
|
|
5351
|
+
const ui = config?.ui;
|
|
5352
|
+
const transport = config?.transport ?? "tool";
|
|
5353
|
+
const theme = useShallowStableRef(config?.theme);
|
|
5354
|
+
const core = (0, react.useMemo)(() => {
|
|
5355
|
+
if (!ui) return null;
|
|
5356
|
+
const reportError = (info) => {
|
|
5357
|
+
try {
|
|
5358
|
+
configRef.current?.errorBoundary?.onError?.(info);
|
|
5359
|
+
} catch (hostError) {
|
|
5360
|
+
if (isDevelopment()) console.error("[generativeUI] errorBoundary.onError threw:", hostError);
|
|
5361
|
+
}
|
|
5362
|
+
if (isDevelopment()) console.warn(`[generativeUI] ${info.kind} error in ${info.surfaceId}${info.componentId ? `/${info.componentId}` : ""}: ${info.error?.message}`);
|
|
5363
|
+
};
|
|
5364
|
+
const manager = new GenerativeUISurfaceManager(ui, { onOperationRejected: ({ surfaceId, index, reason }) => reportError({
|
|
5365
|
+
kind: "operation",
|
|
5366
|
+
surfaceId,
|
|
5367
|
+
error: /* @__PURE__ */ new Error(`operation ${index} rejected: ${reason}`)
|
|
5368
|
+
}) });
|
|
5369
|
+
const dispatcher = createGenerativeUIDispatcher({
|
|
5370
|
+
getDeclared: () => ui.functions,
|
|
5371
|
+
onError: ({ name, origin, error }) => {
|
|
5372
|
+
if ("functionCallId" in origin) {
|
|
5373
|
+
if (isDevelopment()) console.warn(`[generativeUI] function call ${origin.functionCallId} to "${name}" failed: ${error.message}`);
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
reportError({
|
|
5377
|
+
kind: "function",
|
|
5378
|
+
...origin,
|
|
5379
|
+
error
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
});
|
|
5383
|
+
const functionCalls = createFunctionCallRunner({
|
|
5384
|
+
copilotkit,
|
|
5385
|
+
dispatcher,
|
|
5386
|
+
manifest: {
|
|
5387
|
+
catalogId: ui.id,
|
|
5388
|
+
catalogIds: catalogIdsOf(ui),
|
|
5389
|
+
components: ui.manifestComponents
|
|
5390
|
+
}
|
|
5391
|
+
});
|
|
5392
|
+
const forwardingAgents = /* @__PURE__ */ new Set();
|
|
5393
|
+
/**
|
|
5394
|
+
* The action of a user event, as an envelope entry scoped to the agent
|
|
5395
|
+
* the user acted on, for the run that the event starts: the structured
|
|
5396
|
+
* form of the user message. It is set before the run starts and dropped
|
|
5397
|
+
* when the run is over, so a suggestion request or a follow-up run
|
|
5398
|
+
* (both read the properties with no run start) does not carry the same
|
|
5399
|
+
* action again, and another agent never gets a click meant for this one.
|
|
5400
|
+
* The entry comes after the shared envelope, so it wins for its agent;
|
|
5401
|
+
* `refreshActions` keeps it last when the shared envelope is set again.
|
|
5402
|
+
*/
|
|
5403
|
+
const actions = /* @__PURE__ */ new Map();
|
|
5404
|
+
const dropAction = (agent) => {
|
|
5405
|
+
const entry = actions.get(agent);
|
|
5406
|
+
if (!entry) return;
|
|
5407
|
+
entry.subscription.unsubscribe();
|
|
5408
|
+
copilotkit.removeScopedProperties(entry.id);
|
|
5409
|
+
actions.delete(agent);
|
|
5410
|
+
};
|
|
5411
|
+
const setAction = (agent, action) => {
|
|
5412
|
+
const previous = actions.get(agent);
|
|
5413
|
+
previous?.subscription.unsubscribe();
|
|
5414
|
+
if (previous) copilotkit.removeScopedProperties(previous.id);
|
|
5415
|
+
actions.delete(agent);
|
|
5416
|
+
const latest = configRef.current;
|
|
5417
|
+
if (!latest) return;
|
|
5418
|
+
const envelope = buildGenerativeUIEnvelope({
|
|
5419
|
+
ui,
|
|
5420
|
+
manager,
|
|
5421
|
+
dispatcher
|
|
5422
|
+
}, agent.threadId, {
|
|
5423
|
+
...envelopeOptionsFor(latest),
|
|
5424
|
+
action
|
|
5425
|
+
});
|
|
5426
|
+
const id = copilotkit.addScopedProperties({
|
|
5427
|
+
properties: { [_copilotkit_shared.COPILOTKIT_FORWARDED_PROPS_KEY]: { ui: envelope } },
|
|
5428
|
+
agentIds: agent.agentId === void 0 ? latest.agentIds : [agent.agentId]
|
|
5429
|
+
});
|
|
5430
|
+
const subscription = agent.subscribe({
|
|
5431
|
+
onRunFinalized: () => dropAction(agent),
|
|
5432
|
+
onRunFailed: () => dropAction(agent)
|
|
5433
|
+
});
|
|
5434
|
+
actions.set(agent, {
|
|
5435
|
+
id,
|
|
5436
|
+
action,
|
|
5437
|
+
subscription
|
|
5438
|
+
});
|
|
5439
|
+
};
|
|
5440
|
+
const refreshActions = () => {
|
|
5441
|
+
for (const [agent, { action }] of [...actions]) setAction(agent, action);
|
|
5442
|
+
};
|
|
5443
|
+
const dropAllActions = () => {
|
|
5444
|
+
for (const agent of [...actions.keys()]) dropAction(agent);
|
|
5445
|
+
};
|
|
5446
|
+
const forwardEvent = async (event, agentId) => {
|
|
5447
|
+
const fail = (message) => {
|
|
5448
|
+
const error = new GenerativeUIError("event_forward_failed", message);
|
|
5449
|
+
reportError({
|
|
5450
|
+
kind: "event",
|
|
5451
|
+
surfaceId: event.surfaceId,
|
|
5452
|
+
componentId: event.componentId,
|
|
5453
|
+
error
|
|
5454
|
+
});
|
|
5455
|
+
return {
|
|
5456
|
+
ok: false,
|
|
5457
|
+
error
|
|
5458
|
+
};
|
|
5459
|
+
};
|
|
5460
|
+
const agent = copilotkit.getAgent(agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID);
|
|
5461
|
+
if (!agent) return fail(`No agent "${agentId}" to receive "${event.name}"`);
|
|
5462
|
+
if (agent.isRunning || forwardingAgents.has(agent)) return {
|
|
5463
|
+
ok: false,
|
|
5464
|
+
error: new GenerativeUIError("agent_busy", `"${event.name}" was not sent: the agent is running`)
|
|
5465
|
+
};
|
|
5466
|
+
forwardingAgents.add(agent);
|
|
5467
|
+
try {
|
|
5468
|
+
const surface = agent.threadId ? manager.getSurface(agent.threadId, event.surfaceId) : void 0;
|
|
5469
|
+
const resolveLabel = surface ? createComponentLabelResolver(surfaceComponents(surface)) : void 0;
|
|
5470
|
+
agent.addMessage({
|
|
5471
|
+
id: (0, _copilotkit_shared.randomUUID)(),
|
|
5472
|
+
role: "user",
|
|
5473
|
+
content: buildActionMessageText(event, resolveLabel)
|
|
5474
|
+
});
|
|
5475
|
+
setAction(agent, (0, _copilotkit_shared.toA2UIActionMessage)(event));
|
|
5476
|
+
await copilotkit.runAgent({ agent });
|
|
5477
|
+
return {
|
|
5478
|
+
ok: true,
|
|
5479
|
+
result: void 0
|
|
5480
|
+
};
|
|
5481
|
+
} catch (cause) {
|
|
5482
|
+
return fail(`Forwarding "${event.name}" failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
5483
|
+
} finally {
|
|
5484
|
+
forwardingAgents.delete(agent);
|
|
5485
|
+
dropAction(agent);
|
|
5486
|
+
}
|
|
5487
|
+
};
|
|
5488
|
+
return {
|
|
5489
|
+
manager,
|
|
5490
|
+
dispatcher,
|
|
5491
|
+
ui,
|
|
5492
|
+
forwardEvent,
|
|
5493
|
+
reportError,
|
|
5494
|
+
functionCalls,
|
|
5495
|
+
refreshActions,
|
|
5496
|
+
dropAllActions
|
|
5497
|
+
};
|
|
5498
|
+
}, [ui, copilotkit]);
|
|
5499
|
+
const runtime = (0, react.useMemo)(() => {
|
|
5500
|
+
if (!core) return null;
|
|
5501
|
+
const { manager, dispatcher, forwardEvent, reportError } = core;
|
|
5502
|
+
return {
|
|
5503
|
+
manager,
|
|
5504
|
+
dispatcher,
|
|
5505
|
+
ui: core.ui,
|
|
5506
|
+
forwardEvent,
|
|
5507
|
+
reportError,
|
|
5508
|
+
transport,
|
|
5509
|
+
theme,
|
|
5510
|
+
get config() {
|
|
5511
|
+
return configRef.current ?? { ui: core.ui };
|
|
5512
|
+
},
|
|
5513
|
+
get errorFallback() {
|
|
5514
|
+
return configRef.current?.errorBoundary?.fallback;
|
|
5515
|
+
}
|
|
5516
|
+
};
|
|
5517
|
+
}, [
|
|
5518
|
+
core,
|
|
5519
|
+
transport,
|
|
5520
|
+
theme
|
|
5521
|
+
]);
|
|
5522
|
+
(0, react.useEffect)(() => {
|
|
5523
|
+
if (!core) return;
|
|
5524
|
+
const watched = /* @__PURE__ */ new WeakSet();
|
|
5525
|
+
const agentSubscriptions = [];
|
|
5526
|
+
let envelopeId;
|
|
5527
|
+
const debug = createGenerativeUIDebugSource(core, () => {
|
|
5528
|
+
const latest = configRef.current ?? { ui: core.ui };
|
|
5529
|
+
return {
|
|
5530
|
+
options: envelopeOptionsFor(latest),
|
|
5531
|
+
agentIds: latest.agentIds
|
|
5532
|
+
};
|
|
5533
|
+
});
|
|
5534
|
+
copilotkit.ɵsetGenerativeUIDebugSource(debug.source);
|
|
5535
|
+
const removeEnvelope = () => {
|
|
5536
|
+
if (envelopeId === void 0) return;
|
|
5537
|
+
copilotkit.removeScopedProperties(envelopeId);
|
|
5538
|
+
envelopeId = void 0;
|
|
5539
|
+
};
|
|
5540
|
+
/**
|
|
5541
|
+
* Sets the shared envelope of the run. The action entries of the agents
|
|
5542
|
+
* with a user event under way (see `forwardEvent`) are set again after
|
|
5543
|
+
* it, so they stay the last entries and win for their agents.
|
|
5544
|
+
*/
|
|
5545
|
+
const setEnvelope = (agent, latest) => {
|
|
5546
|
+
removeEnvelope();
|
|
5547
|
+
const run = describeGenerativeUIRun(core, agent.threadId, envelopeOptionsFor(latest));
|
|
5548
|
+
envelopeId = copilotkit.addScopedProperties({
|
|
5549
|
+
properties: { [_copilotkit_shared.COPILOTKIT_FORWARDED_PROPS_KEY]: { ui: run.envelope } },
|
|
5550
|
+
agentIds: latest.agentIds
|
|
5551
|
+
});
|
|
5552
|
+
core.refreshActions();
|
|
5553
|
+
if (!latest.agentIds || latest.agentIds.includes(agent.agentId ?? "")) debug.recordRun({
|
|
5554
|
+
...run,
|
|
5555
|
+
agentId: agent.agentId,
|
|
5556
|
+
threadId: agent.threadId,
|
|
5557
|
+
startedAt: Date.now()
|
|
5558
|
+
});
|
|
5559
|
+
};
|
|
5560
|
+
/**
|
|
5561
|
+
* The tool transport reads the surface activity messages. The markdown
|
|
5562
|
+
* transport reads the ```a2ui fences of the assistant messages, and
|
|
5563
|
+
* builds the surfaces of the thread from them again on each change.
|
|
5564
|
+
* Both give their function calls to the one runner.
|
|
5565
|
+
*/
|
|
5566
|
+
const readMessages = (agent, messages, running) => {
|
|
5567
|
+
const requests = activityFunctionCalls(messages);
|
|
5568
|
+
const { threadId } = agent;
|
|
5569
|
+
if (configRef.current?.transport !== "markdown") ingestMessages(core.manager, agent, messages);
|
|
5570
|
+
else if (threadId) {
|
|
5571
|
+
core.manager.setActiveThread(threadId);
|
|
5572
|
+
const calls = getMarkdownSurfaceSource(core).sync(threadId, agent.agentId, messages, { running });
|
|
5573
|
+
requests.push(...markdownFunctionCalls(calls));
|
|
5574
|
+
}
|
|
5575
|
+
core.functionCalls.run(agent, requests, messages, running);
|
|
5576
|
+
};
|
|
5577
|
+
const subscription = copilotkit.subscribe({ onAgentRunStarted: ({ agent }) => {
|
|
5578
|
+
core.manager.setActiveThread(agent.threadId);
|
|
5579
|
+
const latest = configRef.current;
|
|
5580
|
+
if (latest) setEnvelope(agent, latest);
|
|
5581
|
+
if (!watched.has(agent)) {
|
|
5582
|
+
watched.add(agent);
|
|
5583
|
+
agentSubscriptions.push(agent.subscribe({
|
|
5584
|
+
onMessagesChanged: ({ messages }) => readMessages(agent, messages, agent.isRunning),
|
|
5585
|
+
onRunFinalized: () => readMessages(agent, agent.messages, false),
|
|
5586
|
+
onRunFailed: () => readMessages(agent, agent.messages, false)
|
|
5587
|
+
}));
|
|
5588
|
+
}
|
|
5589
|
+
readMessages(agent, agent.messages, true);
|
|
5590
|
+
} });
|
|
5591
|
+
return () => {
|
|
5592
|
+
subscription.unsubscribe();
|
|
5593
|
+
for (const s of agentSubscriptions) s.unsubscribe();
|
|
5594
|
+
core.dropAllActions();
|
|
5595
|
+
removeEnvelope();
|
|
5596
|
+
if (copilotkit.ɵgenerativeUIDebugSource === debug.source) copilotkit.ɵsetGenerativeUIDebugSource(void 0);
|
|
5597
|
+
};
|
|
5598
|
+
}, [core, copilotkit]);
|
|
5599
|
+
useGenerativeUIMarkdownContext(copilotkit, core, {
|
|
5600
|
+
enabled: config?.transport === "markdown" && config.sendContext !== false,
|
|
5601
|
+
agentIds: config?.agentIds
|
|
5602
|
+
});
|
|
5603
|
+
return runtime;
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
//#endregion
|
|
5607
|
+
//#region src/v2/generative-ui/runtime-context.ts
|
|
5608
|
+
const GenerativeUIRuntimeContext = (0, react.createContext)(null);
|
|
5609
|
+
function useGenerativeUIRuntime() {
|
|
5610
|
+
return (0, react.useContext)(GenerativeUIRuntimeContext);
|
|
5611
|
+
}
|
|
5612
|
+
function useRequiredGenerativeUIRuntime() {
|
|
5613
|
+
const runtime = (0, react.useContext)(GenerativeUIRuntimeContext);
|
|
5614
|
+
if (!runtime) throw new Error("Generative UI components must be rendered inside <CopilotKitProvider generativeUI={{ ui }}>.");
|
|
5615
|
+
return runtime;
|
|
5616
|
+
}
|
|
5617
|
+
/**
|
|
5618
|
+
* Reports an error without ever throwing: `reportError` reaches host code
|
|
5619
|
+
* (`onError`), and a throwing host callback must not break rendering or an
|
|
5620
|
+
* event handler.
|
|
5621
|
+
*/
|
|
5622
|
+
function reportErrorSafely(runtime, info) {
|
|
5623
|
+
try {
|
|
5624
|
+
runtime.reportError(info);
|
|
5625
|
+
} catch {}
|
|
5626
|
+
}
|
|
5627
|
+
const GenerativeUINodeContext = (0, react.createContext)(null);
|
|
5628
|
+
|
|
5629
|
+
//#endregion
|
|
5630
|
+
//#region src/v2/generative-ui/hooks.ts
|
|
5631
|
+
const noopSubscribe$1 = () => () => {};
|
|
5632
|
+
const zero = () => 0;
|
|
5633
|
+
/** Re-renders on any surface-manager change. */
|
|
5634
|
+
function useManagerVersion(runtime) {
|
|
5635
|
+
return (0, react.useSyncExternalStore)(runtime?.manager.subscribe ?? noopSubscribe$1, runtime?.manager.getVersion ?? zero, zero);
|
|
5636
|
+
}
|
|
5637
|
+
|
|
5638
|
+
//#endregion
|
|
5639
|
+
//#region src/v2/generative-ui/boundaries.tsx
|
|
5640
|
+
function DefaultNodeFallback({ message, componentId, retry }) {
|
|
5641
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5642
|
+
role: "alert",
|
|
5643
|
+
"data-generative-ui-error": componentId,
|
|
5644
|
+
style: {
|
|
5645
|
+
border: "1px solid #fca5a5",
|
|
5646
|
+
background: "#fef2f2",
|
|
5647
|
+
color: "#991b1b",
|
|
5648
|
+
borderRadius: 8,
|
|
5649
|
+
padding: "8px 12px",
|
|
5650
|
+
fontSize: 13
|
|
5651
|
+
},
|
|
5652
|
+
children: [
|
|
5653
|
+
"Couldn’t display this part: ",
|
|
5654
|
+
message,
|
|
5655
|
+
retry && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5656
|
+
type: "button",
|
|
5657
|
+
onClick: retry,
|
|
5658
|
+
style: {
|
|
5659
|
+
marginLeft: 8,
|
|
5660
|
+
textDecoration: "underline"
|
|
5661
|
+
},
|
|
5662
|
+
children: "Retry"
|
|
5663
|
+
})
|
|
5664
|
+
]
|
|
5665
|
+
});
|
|
5666
|
+
}
|
|
5667
|
+
/**
|
|
5668
|
+
* A child that has not streamed yet renders nothing. The prose keeps
|
|
5669
|
+
* streaming above the surface, and each component appears as soon as it is
|
|
5670
|
+
* complete. A grey block in its place would read as a broken layout.
|
|
5671
|
+
*/
|
|
5672
|
+
function NodePlaceholder(_props) {
|
|
5673
|
+
return null;
|
|
5674
|
+
}
|
|
5675
|
+
/** Per-node boundary. Resets on retry or a new agent revision, not on data ticks. */
|
|
5676
|
+
var NodeBoundary = class extends react.Component {
|
|
5677
|
+
constructor(..._args) {
|
|
5678
|
+
super(..._args);
|
|
5679
|
+
this.state = {
|
|
5680
|
+
error: null,
|
|
5681
|
+
revision: this.props.revision
|
|
5682
|
+
};
|
|
5683
|
+
this.retry = () => this.setState({ error: null });
|
|
5684
|
+
}
|
|
5685
|
+
static getDerivedStateFromError(error) {
|
|
5686
|
+
return { error };
|
|
5687
|
+
}
|
|
5688
|
+
static getDerivedStateFromProps(props, state) {
|
|
5689
|
+
return props.revision !== state.revision ? {
|
|
5690
|
+
error: null,
|
|
5691
|
+
revision: props.revision
|
|
5692
|
+
} : null;
|
|
5693
|
+
}
|
|
5694
|
+
componentDidCatch(error) {
|
|
5695
|
+
const { runtime, surfaceId, componentId } = this.props;
|
|
5696
|
+
reportErrorSafely(runtime, {
|
|
5697
|
+
kind: "render",
|
|
5698
|
+
surfaceId,
|
|
5699
|
+
componentId,
|
|
5700
|
+
error
|
|
5701
|
+
});
|
|
5702
|
+
}
|
|
5703
|
+
render() {
|
|
5704
|
+
const { error } = this.state;
|
|
5705
|
+
if (!error) return this.props.children;
|
|
5706
|
+
const { runtime, surfaceId, componentId } = this.props;
|
|
5707
|
+
const fallback = runtime.errorFallback;
|
|
5708
|
+
return fallback ? fallback({
|
|
5709
|
+
error,
|
|
5710
|
+
retry: this.retry,
|
|
5711
|
+
surfaceId,
|
|
5712
|
+
componentId
|
|
5713
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultNodeFallback, {
|
|
5714
|
+
message: error.message,
|
|
5715
|
+
componentId,
|
|
5716
|
+
retry: this.retry
|
|
5717
|
+
});
|
|
5718
|
+
}
|
|
5719
|
+
};
|
|
5720
|
+
/** Outer boundary: a failure here must not reach the chat or the host page. */
|
|
5721
|
+
var SurfaceBoundary = class extends react.Component {
|
|
5722
|
+
constructor(..._args2) {
|
|
5723
|
+
super(..._args2);
|
|
5724
|
+
this.state = { error: null };
|
|
5725
|
+
}
|
|
5726
|
+
static getDerivedStateFromError(error) {
|
|
5727
|
+
return { error };
|
|
5728
|
+
}
|
|
5729
|
+
componentDidCatch(error) {
|
|
5730
|
+
reportErrorSafely(this.props.runtime, {
|
|
5731
|
+
kind: "render",
|
|
5732
|
+
surfaceId: this.props.surfaceId,
|
|
5733
|
+
error
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
render() {
|
|
5737
|
+
if (!this.state.error) return this.props.children;
|
|
5738
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultNodeFallback, {
|
|
5739
|
+
message: "this generated UI failed to render",
|
|
5740
|
+
componentId: "surface"
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5743
|
+
};
|
|
5744
|
+
|
|
5745
|
+
//#endregion
|
|
5746
|
+
//#region src/v2/generative-ui/accessibility.ts
|
|
5747
|
+
const LIVE = new Set([
|
|
5748
|
+
"off",
|
|
5749
|
+
"polite",
|
|
5750
|
+
"assertive"
|
|
5751
|
+
]);
|
|
5752
|
+
/**
|
|
5753
|
+
* The accessibility of a component from its resolved `accessibility`
|
|
5754
|
+
* value. Fields of the wrong type are left out: a label that is still a
|
|
5755
|
+
* binding, or a list, names nothing.
|
|
5756
|
+
*/
|
|
5757
|
+
function readAccessibility(value) {
|
|
5758
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
5759
|
+
const { label, description, live, hidden } = value;
|
|
5760
|
+
const result = {};
|
|
5761
|
+
if (typeof label === "string" && label !== "") result.label = label;
|
|
5762
|
+
if (typeof description === "string" && description !== "") result.description = description;
|
|
5763
|
+
if (typeof live === "string" && LIVE.has(live)) result.live = live;
|
|
5764
|
+
if (typeof hidden === "boolean") result.hidden = hidden;
|
|
5765
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
5766
|
+
}
|
|
5767
|
+
/**
|
|
5768
|
+
* The ARIA attributes for an `accessibility`. `live: "off"` is written out:
|
|
5769
|
+
* a region inherits the live setting of its ancestors, and a `status` role
|
|
5770
|
+
* is live on its own, so only the explicit value silences it.
|
|
5771
|
+
*
|
|
5772
|
+
* @example
|
|
5773
|
+
* ```ts
|
|
5774
|
+
* accessibilityAttributes({ label: "Totals", live: "polite" });
|
|
5775
|
+
* // { "aria-label": "Totals", "aria-live": "polite" }
|
|
5776
|
+
* ```
|
|
5777
|
+
*/
|
|
5778
|
+
function accessibilityAttributes(accessibility) {
|
|
5779
|
+
if (!accessibility) return {};
|
|
5780
|
+
const { label, description, live, hidden } = accessibility;
|
|
5781
|
+
return {
|
|
5782
|
+
...label === void 0 ? {} : { "aria-label": label },
|
|
5783
|
+
...description === void 0 ? {} : { "aria-description": description },
|
|
5784
|
+
...live === void 0 ? {} : { "aria-live": live },
|
|
5785
|
+
...hidden ? { "aria-hidden": true } : {}
|
|
5786
|
+
};
|
|
5787
|
+
}
|
|
5788
|
+
/**
|
|
5789
|
+
* The part of the attributes for the wrapper around a control: hidden and
|
|
5790
|
+
* live cover the label and the control together.
|
|
5791
|
+
*/
|
|
5792
|
+
function wrapperAria(aria) {
|
|
5793
|
+
const { "aria-hidden": hidden, "aria-live": live } = aria;
|
|
5794
|
+
return {
|
|
5795
|
+
...hidden === void 0 ? {} : { "aria-hidden": hidden },
|
|
5796
|
+
...live === void 0 ? {} : { "aria-live": live }
|
|
5797
|
+
};
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
//#endregion
|
|
5801
|
+
//#region src/v2/generative-ui/events.ts
|
|
5802
|
+
/**
|
|
5803
|
+
* The context of a running event binding: it reads the surface data model
|
|
5804
|
+
* and resolves relative paths against the template item of the component.
|
|
5805
|
+
* A function that needs a user gesture (openUrl) is not a value here
|
|
5806
|
+
* either; it runs through the dispatcher as the binding's functionCall.
|
|
5807
|
+
*/
|
|
5808
|
+
function eventContext(surface, basePath) {
|
|
5809
|
+
return {
|
|
5810
|
+
data: surface.model.dataModel,
|
|
5811
|
+
basePath
|
|
5812
|
+
};
|
|
5813
|
+
}
|
|
5814
|
+
/**
|
|
5815
|
+
* A value of an event binding with its dynamic values resolved: `{"@path"}`
|
|
5816
|
+
* bindings and function values, at any depth.
|
|
5817
|
+
*/
|
|
5818
|
+
function resolveValue(surface, basePath, value) {
|
|
5819
|
+
return resolveDynamicValue(value, eventContext(surface, basePath)).value;
|
|
5820
|
+
}
|
|
5821
|
+
/**
|
|
5822
|
+
* The entries of an args or context record, each resolved on its own. The
|
|
5823
|
+
* record is not resolved as a whole, because a record with one `event` or
|
|
5824
|
+
* `functionCall` key would look like an action binding and stay as it is.
|
|
5825
|
+
*/
|
|
5826
|
+
function resolveValues(surface, basePath, values) {
|
|
5827
|
+
return Object.fromEntries(Object.entries(values ?? {}).map(([key, value]) => [key, resolveValue(surface, basePath, value)]));
|
|
5828
|
+
}
|
|
5829
|
+
/**
|
|
5830
|
+
* The `userMessage` of an event binding, as text. A `{"@path"}` or a
|
|
5831
|
+
* function value is resolved. `undefined` when there is no message, or
|
|
5832
|
+
* the value is not a string.
|
|
5833
|
+
*/
|
|
5834
|
+
function resolveUserMessage(surface, basePath, userMessage) {
|
|
5835
|
+
const value = resolveValue(surface, basePath, userMessage);
|
|
5836
|
+
return typeof value === "string" ? value : void 0;
|
|
5837
|
+
}
|
|
5838
|
+
/**
|
|
5839
|
+
* Validates the component's args with the event `parameters`, then runs
|
|
5840
|
+
* `send` with them. Never throws: every failure is reported and becomes
|
|
5841
|
+
* `{ ok: false, error }`.
|
|
5842
|
+
*/
|
|
5843
|
+
async function runEvent(call, send) {
|
|
5844
|
+
const { runtime, surface, componentId, name, parameters, args } = call;
|
|
5845
|
+
const origin = {
|
|
5846
|
+
surfaceId: surface.surfaceId,
|
|
5847
|
+
componentId
|
|
5848
|
+
};
|
|
5849
|
+
try {
|
|
5850
|
+
if (!parameters) return await send(args ?? {}, origin);
|
|
5851
|
+
const result = validateSchema(parameters, args ?? {});
|
|
5852
|
+
const parsed = result instanceof Promise ? await result : result;
|
|
5853
|
+
if (!parsed.success) {
|
|
5854
|
+
const error = new GenerativeUIError("invalid_event_args", `${name}: ${formatIssues(parsed.issues, "args")}`);
|
|
5855
|
+
reportErrorSafely(runtime, {
|
|
5856
|
+
kind: "event",
|
|
5857
|
+
...origin,
|
|
5858
|
+
error
|
|
5859
|
+
});
|
|
5860
|
+
return {
|
|
5861
|
+
ok: false,
|
|
5862
|
+
error
|
|
5863
|
+
};
|
|
5864
|
+
}
|
|
5865
|
+
return await send(parsed.data ?? {}, origin);
|
|
5866
|
+
} catch (cause) {
|
|
5867
|
+
const error = new GenerativeUIError("event_forward_failed", `${name}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
5868
|
+
reportErrorSafely(runtime, {
|
|
5869
|
+
kind: "event",
|
|
5870
|
+
...origin,
|
|
5871
|
+
error
|
|
5872
|
+
});
|
|
5873
|
+
return {
|
|
5874
|
+
ok: false,
|
|
5875
|
+
error
|
|
5876
|
+
};
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
/**
|
|
5880
|
+
* Runs one call of an event that the model bound: validates the component's
|
|
5881
|
+
* args, merges them with the args from the binding (binding literals win),
|
|
5882
|
+
* then calls a declared function or forwards an agent event. Never throws.
|
|
5883
|
+
*/
|
|
5884
|
+
function invokeBoundEvent(params) {
|
|
5885
|
+
const { runtime, surface, binding, basePath } = params;
|
|
5886
|
+
return runEvent(params, (args, origin) => {
|
|
5887
|
+
const componentArgs = args;
|
|
5888
|
+
if ("functionCall" in binding) return runtime.dispatcher.invoke(binding.functionCall["@call"], {
|
|
5889
|
+
...componentArgs,
|
|
5890
|
+
...resolveValues(surface, basePath, binding.functionCall.args)
|
|
5891
|
+
}, origin);
|
|
5892
|
+
const { event } = binding;
|
|
5893
|
+
const forwarded = {
|
|
5894
|
+
name: event.name,
|
|
5895
|
+
...origin,
|
|
5896
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5897
|
+
userMessage: resolveUserMessage(surface, basePath, event.userMessage),
|
|
5898
|
+
context: {
|
|
5899
|
+
...componentArgs,
|
|
5900
|
+
...resolveValues(surface, basePath, event.context)
|
|
5901
|
+
}
|
|
5902
|
+
};
|
|
5903
|
+
return runtime.forwardEvent(forwarded, surface.agentId);
|
|
5904
|
+
});
|
|
5905
|
+
}
|
|
5906
|
+
/**
|
|
5907
|
+
* Runs one call of an event that matches a declared function: validates the
|
|
5908
|
+
* component's args with the event `parameters` (if any), then calls the
|
|
5909
|
+
* function, which validates them with its own schema. Never throws.
|
|
5910
|
+
*/
|
|
5911
|
+
function invokeMatchedEvent(params) {
|
|
5912
|
+
const { runtime, functionName } = params;
|
|
5913
|
+
return runEvent(params, (componentArgs, origin) => runtime.dispatcher.invoke(functionName, componentArgs, origin));
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
//#endregion
|
|
5917
|
+
//#region src/v2/generative-ui/theme.ts
|
|
5918
|
+
/**
|
|
5919
|
+
* The built-in dark values. The light values are the fallbacks in the
|
|
5920
|
+
* components, so a surface is light when no variable is set.
|
|
5921
|
+
*/
|
|
5922
|
+
const DARK_THEME = {
|
|
5923
|
+
"--a2ui-color-text-default": "#ececec",
|
|
5924
|
+
"--a2ui-color-text-prose": "#ececec",
|
|
5925
|
+
"--a2ui-color-text-primary": "#ececec",
|
|
5926
|
+
"--a2ui-color-text-emphasis": "#fff",
|
|
5927
|
+
"--a2ui-color-text-secondary": "#b4b4b4",
|
|
5928
|
+
"--a2ui-color-text-tertiary": "#8e8e8e",
|
|
5929
|
+
"--a2ui-color-text-success": "#4fd1a5",
|
|
5930
|
+
"--a2ui-color-text-info": "#6cb6ff",
|
|
5931
|
+
"--a2ui-color-text-warning": "#e3b341",
|
|
5932
|
+
"--a2ui-color-text-caution": "#e3b341",
|
|
5933
|
+
"--a2ui-color-text-danger": "#ff7b72",
|
|
5934
|
+
"--a2ui-color-text-discovery": "#bc9cff",
|
|
5935
|
+
"--a2ui-color-surface": "#1f1f1f",
|
|
5936
|
+
"--a2ui-color-surface-secondary": "#2a2a2a",
|
|
5937
|
+
"--a2ui-color-surface-tertiary": "#3a3a3a",
|
|
5938
|
+
"--a2ui-color-surface-elevated": "#262626",
|
|
5939
|
+
"--a2ui-color-surface-elevated-secondary": "#2f2f2f",
|
|
5940
|
+
"--a2ui-color-surface-inverted": "#f2f2f2",
|
|
5941
|
+
"--a2ui-color-surface-soft": "#232323",
|
|
5942
|
+
"--a2ui-color-info": "rgba(80, 160, 255, 0.14)",
|
|
5943
|
+
"--a2ui-color-warning": "rgba(240, 180, 60, 0.14)",
|
|
5944
|
+
"--a2ui-color-caution": "rgba(240, 180, 60, 0.14)",
|
|
5945
|
+
"--a2ui-color-success": "rgba(60, 200, 150, 0.14)",
|
|
5946
|
+
"--a2ui-color-danger": "rgba(255, 110, 100, 0.14)",
|
|
5947
|
+
"--a2ui-color-discovery": "rgba(170, 130, 255, 0.14)",
|
|
5948
|
+
"--a2ui-color-info-soft": "rgba(80, 160, 255, 0.08)",
|
|
5949
|
+
"--a2ui-color-warning-soft": "rgba(240, 180, 60, 0.08)",
|
|
5950
|
+
"--a2ui-color-caution-soft": "rgba(240, 180, 60, 0.08)",
|
|
5951
|
+
"--a2ui-color-success-soft": "rgba(60, 200, 150, 0.08)",
|
|
5952
|
+
"--a2ui-color-danger-soft": "rgba(255, 110, 100, 0.08)",
|
|
5953
|
+
"--a2ui-color-discovery-soft": "rgba(170, 130, 255, 0.08)",
|
|
5954
|
+
"--a2ui-color-info-solid": "#3b8eea",
|
|
5955
|
+
"--a2ui-color-warning-solid": "#c99a2e",
|
|
5956
|
+
"--a2ui-color-caution-solid": "#c99a2e",
|
|
5957
|
+
"--a2ui-color-success-solid": "#2fae86",
|
|
5958
|
+
"--a2ui-color-danger-solid": "#e5534b",
|
|
5959
|
+
"--a2ui-color-discovery-solid": "#9a6ff0",
|
|
5960
|
+
"--a2ui-color-border-default": "rgba(255, 255, 255, 0.12)",
|
|
5961
|
+
"--a2ui-color-border-subtle": "rgba(255, 255, 255, 0.06)",
|
|
5962
|
+
"--a2ui-color-border-strong": "#4a4a4a",
|
|
5963
|
+
"--a2ui-color-border-info": "#2f5f8f",
|
|
5964
|
+
"--a2ui-color-border-warning": "#6b5420",
|
|
5965
|
+
"--a2ui-color-border-caution": "#6b5420",
|
|
5966
|
+
"--a2ui-color-border-success": "#1f5f4b",
|
|
5967
|
+
"--a2ui-color-border-danger": "#7a2f2b",
|
|
5968
|
+
"--a2ui-color-border-discovery": "#4f3a80",
|
|
5969
|
+
"--a2ui-color-border-control": "rgba(255, 255, 255, 0.2)",
|
|
5970
|
+
"--a2ui-color-accent": "#f2f2f2",
|
|
5971
|
+
"--a2ui-color-on-accent": "#0d0d0d",
|
|
5972
|
+
"--a2ui-color-panel": "#262626",
|
|
5973
|
+
"--a2ui-color-control": "#2f2f2f",
|
|
5974
|
+
"--a2ui-color-focus": "#3b9bff",
|
|
5975
|
+
"--a2ui-color-shimmer": "rgba(255, 255, 255, 0.12)",
|
|
5976
|
+
"--a2ui-color-chart-cursor": "rgba(255, 255, 255, 0.06)",
|
|
5977
|
+
"--a2ui-color-slider-track": "rgba(255, 255, 255, 0.1)",
|
|
5978
|
+
"--a2ui-color-badge-secondary": "rgba(255, 255, 255, 0.08)",
|
|
5979
|
+
"--a2ui-color-highlight-green": "#1d4a37",
|
|
5980
|
+
"--a2ui-color-highlight-orange": "#4d3418",
|
|
5981
|
+
"--a2ui-color-highlight-pink": "#4d2338",
|
|
5982
|
+
"--a2ui-shadow-xs": "0 1px 2px rgba(0, 0, 0, 0.5)",
|
|
5983
|
+
"--a2ui-shadow-sm": "0 1px 3px rgba(0, 0, 0, 0.5)",
|
|
5984
|
+
"--a2ui-shadow-soft": "0 1px 3px rgba(0, 0, 0, 0.3)",
|
|
5985
|
+
"--a2ui-shadow-md": "0 4px 12px rgba(0, 0, 0, 0.4)"
|
|
5986
|
+
};
|
|
5987
|
+
const VARIABLE_NAME = /^--a2ui-[a-z0-9-]+$/;
|
|
5988
|
+
const UNSAFE_VALUE = /[{};<>]/;
|
|
5989
|
+
/** Matches the `dark` class on an ancestor of a surface, or on the surface. */
|
|
5990
|
+
const DARK = ":where(.dark)";
|
|
5991
|
+
/** The default dark values, as a rule that any theme rule beats. */
|
|
5992
|
+
const DARK_THEME_CSS = `:where(.dark .a2ui-surface,.a2ui-surface.dark){${declarations(DARK_THEME)}}`;
|
|
5993
|
+
function declarations(values) {
|
|
5994
|
+
return Object.entries(values ?? {}).filter(([name, value]) => VARIABLE_NAME.test(name) && typeof value === "string" && !UNSAFE_VALUE.test(value)).map(([name, value]) => `${name}:${value}`).join(";");
|
|
5995
|
+
}
|
|
5996
|
+
/** A short, stable id for a stylesheet. */
|
|
5997
|
+
function hashOf(text) {
|
|
5998
|
+
let hash = 5381;
|
|
5999
|
+
for (let index = 0; index < text.length; index++) hash = hash * 33 ^ text.charCodeAt(index);
|
|
6000
|
+
return `t${(hash >>> 0).toString(36)}`;
|
|
6001
|
+
}
|
|
6002
|
+
/**
|
|
6003
|
+
* The stylesheet of a theme: its values on the surfaces that carry its id,
|
|
6004
|
+
* then its dark values in dark mode. `undefined` when the theme sets no
|
|
6005
|
+
* variable. Values that could break out of the rule are left out.
|
|
6006
|
+
*
|
|
6007
|
+
* @example
|
|
6008
|
+
* ```ts
|
|
6009
|
+
* themeStyleSheet({ "--a2ui-color-accent": "red" });
|
|
6010
|
+
* // { id: "t…", css: '.a2ui-surface[data-a2ui-theme="t…"]{--a2ui-color-accent:red}' }
|
|
6011
|
+
* ```
|
|
6012
|
+
*/
|
|
6013
|
+
function themeStyleSheet(theme) {
|
|
6014
|
+
const { dark, ...base } = theme ?? {};
|
|
6015
|
+
const both = declarations(base);
|
|
6016
|
+
const darkOnly = declarations(dark);
|
|
6017
|
+
if (!both && !darkOnly) return void 0;
|
|
6018
|
+
const id = hashOf(`${both}|${darkOnly}`);
|
|
6019
|
+
const self = `.a2ui-surface[data-a2ui-theme="${id}"]`;
|
|
6020
|
+
return {
|
|
6021
|
+
id,
|
|
6022
|
+
css: [both && `${self}{${both}}`, darkOnly && `${DARK} ${self},${self}${DARK}{${darkOnly}}`].filter(Boolean).join("")
|
|
6023
|
+
};
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
//#endregion
|
|
6027
|
+
//#region src/v2/generative-ui/SurfaceView.tsx
|
|
6028
|
+
const NO_ANCESTORS = [];
|
|
6029
|
+
const ROOT_STYLE = { display: "contents" };
|
|
6030
|
+
const undefinedSnapshot = () => void 0;
|
|
6031
|
+
const noopSubscribe = () => () => {};
|
|
6032
|
+
const zeroSnapshot = () => 0;
|
|
6033
|
+
/** A "multiple" slot value that repeats a component for each list item. */
|
|
6034
|
+
function isTemplateChildren(value) {
|
|
6035
|
+
return typeof value === "object" && value !== null && "componentId" in value && typeof value.componentId === "string" && "path" in value && typeof value.path === "string";
|
|
6036
|
+
}
|
|
6037
|
+
function SurfaceView({ surface }) {
|
|
6038
|
+
const runtime = useRequiredGenerativeUIRuntime();
|
|
6039
|
+
const { theme } = runtime;
|
|
6040
|
+
const sheet = (0, react.useMemo)(() => themeStyleSheet(theme), [theme]);
|
|
6041
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6042
|
+
className: "a2ui-surface",
|
|
6043
|
+
"data-generative-ui-root": surface.surfaceId,
|
|
6044
|
+
"data-a2ui-theme": sheet?.id,
|
|
6045
|
+
style: ROOT_STYLE,
|
|
6046
|
+
children: [sheet ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: sheet.css } }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceBoundary, {
|
|
6047
|
+
surfaceId: surface.surfaceId,
|
|
6048
|
+
runtime,
|
|
6049
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6050
|
+
surface,
|
|
6051
|
+
componentId: _copilotkit_shared.GENERATIVE_UI_ROOT_ID,
|
|
6052
|
+
basePath: void 0,
|
|
6053
|
+
ancestors: NO_ANCESTORS
|
|
6054
|
+
})
|
|
6055
|
+
})]
|
|
6056
|
+
});
|
|
6057
|
+
}
|
|
6058
|
+
/**
|
|
6059
|
+
* Resolves a component by id. Subscribes to the manager rather than web_core's
|
|
6060
|
+
* `onCreated`/`onDeleted`: web_core's EventEmitter awaits each listener in turn,
|
|
6061
|
+
* so with several subscribers (one per slotted child, or one per mount of the
|
|
6062
|
+
* same surface) all but the first are notified a microtask late. That is only
|
|
6063
|
+
* visible inside synchronous flushes such as `act()` / `flushSync`, where the
|
|
6064
|
+
* old tree would still be on screen, but it makes updates order-dependent.
|
|
6065
|
+
* The manager notifies synchronously once an ingest has been fully applied.
|
|
6066
|
+
*/
|
|
6067
|
+
function useComponentModel(manager, model, id) {
|
|
6068
|
+
return (0, react.useSyncExternalStore)(manager.subscribe, () => model.componentsModel.get(id), undefinedSnapshot);
|
|
6069
|
+
}
|
|
6070
|
+
/** Same reasoning as `useComponentModel`: never rely on web_core's emitters. */
|
|
6071
|
+
function useComponentProperties(manager, component) {
|
|
6072
|
+
return (0, react.useSyncExternalStore)(manager.subscribe, () => component?.properties, undefinedSnapshot);
|
|
6073
|
+
}
|
|
6074
|
+
/**
|
|
6075
|
+
* The id of a node in the ancestor list. A template renders the same
|
|
6076
|
+
* component once per item, so the item path is part of the id. A real cycle
|
|
6077
|
+
* repeats both.
|
|
6078
|
+
*/
|
|
6079
|
+
function scopedId(componentId, basePath) {
|
|
6080
|
+
return `${basePath ?? ""}#${componentId}`;
|
|
6081
|
+
}
|
|
6082
|
+
const NodeView = (0, react.memo)(function NodeView({ surface, componentId, basePath, ancestors }) {
|
|
6083
|
+
const runtime = useRequiredGenerativeUIRuntime();
|
|
6084
|
+
const component = useComponentModel(runtime.manager, surface.model, componentId);
|
|
6085
|
+
const properties = useComponentProperties(runtime.manager, component);
|
|
6086
|
+
if (ancestors.includes(scopedId(componentId, basePath))) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultNodeFallback, {
|
|
6087
|
+
message: `cyclic reference to ${componentId}`,
|
|
6088
|
+
componentId
|
|
6089
|
+
});
|
|
6090
|
+
if (!component || !properties) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodePlaceholder, { componentId });
|
|
6091
|
+
const definition = runtime.ui.components.get(component.type);
|
|
6092
|
+
if (!definition) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultNodeFallback, {
|
|
6093
|
+
message: `unknown component "${component.type}"`,
|
|
6094
|
+
componentId
|
|
6095
|
+
});
|
|
6096
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeBoundary, {
|
|
6097
|
+
surfaceId: surface.surfaceId,
|
|
6098
|
+
componentId,
|
|
6099
|
+
revision: properties,
|
|
6100
|
+
runtime,
|
|
6101
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResolvedNode, {
|
|
6102
|
+
surface,
|
|
6103
|
+
componentId,
|
|
6104
|
+
definition,
|
|
6105
|
+
properties,
|
|
6106
|
+
runtime,
|
|
6107
|
+
basePath,
|
|
6108
|
+
ancestors
|
|
6109
|
+
})
|
|
6110
|
+
}, component.type);
|
|
6111
|
+
});
|
|
6112
|
+
/**
|
|
6113
|
+
* Renders the component of a template once for each item of the list at the
|
|
6114
|
+
* template path, keyed by index. Renders nothing when there is no list.
|
|
6115
|
+
*/
|
|
6116
|
+
function TemplateChildren({ surface, template, basePath, ancestors }) {
|
|
6117
|
+
const dataModel = surface.model.dataModel;
|
|
6118
|
+
const listPath = resolvePath(template.path, basePath);
|
|
6119
|
+
const subscribe = (0, react.useCallback)((onChange) => {
|
|
6120
|
+
const subscription = dataModel.subscribe(listPath, onChange);
|
|
6121
|
+
return () => subscription.unsubscribe();
|
|
6122
|
+
}, [dataModel, listPath]);
|
|
6123
|
+
const getCount = () => {
|
|
6124
|
+
const list = dataModel.get(listPath);
|
|
6125
|
+
return Array.isArray(list) ? list.length : 0;
|
|
6126
|
+
};
|
|
6127
|
+
const count = (0, react.useSyncExternalStore)(subscribe, getCount, zeroSnapshot);
|
|
6128
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Array.from({ length: count }, (_, index) => index).map((index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6129
|
+
surface,
|
|
6130
|
+
componentId: template.componentId,
|
|
6131
|
+
basePath: joinPath(listPath, String(index)),
|
|
6132
|
+
ancestors
|
|
6133
|
+
}, index)) });
|
|
6134
|
+
}
|
|
6135
|
+
/**
|
|
6136
|
+
* The props the model sets, with their dynamic values resolved from the
|
|
6137
|
+
* surface data model: `{"@path"}` bindings, `{"@call"}` function values and
|
|
6138
|
+
* `{"@call":"@index"}` in a template, at any depth. A literal writable prop
|
|
6139
|
+
* that the component set shows the local value from `overrides`. Bound
|
|
6140
|
+
* props, events and slots are left out, and so are the keys of the A2UI
|
|
6141
|
+
* component envelope; `accessibility` is resolved the same way and given
|
|
6142
|
+
* on its own.
|
|
6143
|
+
*
|
|
6144
|
+
* A prop is resolved again only when its raw value or a data model value it
|
|
6145
|
+
* read has changed, so the result keeps its identity otherwise. A function
|
|
6146
|
+
* value that cannot be computed gives a problem instead of values.
|
|
6147
|
+
*/
|
|
6148
|
+
function useModelDataProps(surface, definition, properties, basePath, overrides) {
|
|
6149
|
+
const dataModel = surface.model.dataModel;
|
|
6150
|
+
const units = (0, react.useMemo)(() => {
|
|
6151
|
+
const list = [];
|
|
6152
|
+
for (const key of Object.keys(properties)) {
|
|
6153
|
+
if (definition.events?.[key] || definition.slots?.[key] || Object.hasOwn(definition.selectors, key) || _copilotkit_shared.A2UI_COMPONENT_ENVELOPE_KEYS.has(key)) continue;
|
|
6154
|
+
list.push({
|
|
6155
|
+
key,
|
|
6156
|
+
raw: properties[key]
|
|
6157
|
+
});
|
|
6158
|
+
}
|
|
6159
|
+
const a11y = properties.accessibility;
|
|
6160
|
+
if (typeof a11y === "object" && a11y !== null && !Array.isArray(a11y)) for (const [field, raw] of Object.entries(a11y)) list.push({
|
|
6161
|
+
key: `accessibility/${field}`,
|
|
6162
|
+
raw,
|
|
6163
|
+
field
|
|
6164
|
+
});
|
|
6165
|
+
return list;
|
|
6166
|
+
}, [properties, definition]);
|
|
6167
|
+
const [, rerender] = (0, react.useReducer)((n) => n + 1, 0);
|
|
6168
|
+
const cache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
6169
|
+
const values = {};
|
|
6170
|
+
const a11yValues = {};
|
|
6171
|
+
const paths = /* @__PURE__ */ new Set();
|
|
6172
|
+
let problem;
|
|
6173
|
+
const next = /* @__PURE__ */ new Map();
|
|
6174
|
+
for (const { key, raw, field } of units) {
|
|
6175
|
+
const cached = cache.current.get(key);
|
|
6176
|
+
const isFresh = cached !== void 0 && cached.raw === raw && cached.basePath === basePath && [...cached.reads].every(([path, value]) => Object.is(dataModel.get(path), value));
|
|
6177
|
+
const resolve = () => {
|
|
6178
|
+
const resolved = tryResolveDynamicValue(raw, {
|
|
6179
|
+
data: dataModel,
|
|
6180
|
+
basePath
|
|
6181
|
+
}, { isChecks: key === "checks" });
|
|
6182
|
+
const reads = /* @__PURE__ */ new Map();
|
|
6183
|
+
for (const path of resolved.paths) reads.set(path, dataModel.get(path));
|
|
6184
|
+
return {
|
|
6185
|
+
raw,
|
|
6186
|
+
basePath,
|
|
6187
|
+
reads,
|
|
6188
|
+
outcome: resolved.ok ? {
|
|
6189
|
+
ok: true,
|
|
6190
|
+
value: resolved.value
|
|
6191
|
+
} : {
|
|
6192
|
+
ok: false,
|
|
6193
|
+
message: resolved.error.message
|
|
6194
|
+
}
|
|
6195
|
+
};
|
|
6196
|
+
};
|
|
6197
|
+
const entry = isFresh && cached ? cached : resolve();
|
|
6198
|
+
next.set(key, entry);
|
|
6199
|
+
if (field !== void 0) {
|
|
6200
|
+
if (entry.outcome.ok) a11yValues[field] = entry.outcome.value;
|
|
6201
|
+
} else if (entry.outcome.ok) values[key] = entry.outcome.value;
|
|
6202
|
+
else problem ?? (problem = `${key}: ${entry.outcome.message}`);
|
|
6203
|
+
for (const path of entry.reads.keys()) paths.add(path);
|
|
6204
|
+
}
|
|
6205
|
+
cache.current = next;
|
|
6206
|
+
const accessibility = readAccessibility(a11yValues);
|
|
6207
|
+
for (const [key, value] of Object.entries(overrides)) if (!(0, _copilotkit_shared.isPathBinding)(properties[key])) values[key] = value;
|
|
6208
|
+
const rendered = (0, react.useRef)(next);
|
|
6209
|
+
rendered.current = next;
|
|
6210
|
+
const pathKey = [...paths].sort().join("\n");
|
|
6211
|
+
(0, react.useEffect)(() => {
|
|
6212
|
+
const list = pathKey === "" ? [] : pathKey.split("\n");
|
|
6213
|
+
const onChange = () => {
|
|
6214
|
+
cache.current = /* @__PURE__ */ new Map();
|
|
6215
|
+
rerender();
|
|
6216
|
+
};
|
|
6217
|
+
const subscriptions = list.map((path) => dataModel.subscribe(path, onChange));
|
|
6218
|
+
if ([...rendered.current.values()].some((entry) => [...entry.reads].some(([path, value]) => !Object.is(dataModel.get(path), value)))) rerender();
|
|
6219
|
+
return () => subscriptions.forEach((s) => s.unsubscribe());
|
|
6220
|
+
}, [dataModel, pathKey]);
|
|
6221
|
+
const stable = (0, react.useRef)({
|
|
6222
|
+
ok: true,
|
|
6223
|
+
values: {},
|
|
6224
|
+
accessibility: void 0
|
|
6225
|
+
});
|
|
6226
|
+
const result = problem === void 0 ? {
|
|
6227
|
+
ok: true,
|
|
6228
|
+
values,
|
|
6229
|
+
accessibility
|
|
6230
|
+
} : {
|
|
6231
|
+
ok: false,
|
|
6232
|
+
problem
|
|
6233
|
+
};
|
|
6234
|
+
const last = stable.current;
|
|
6235
|
+
if (!(result.ok && last.ok ? shallowEqual(last.values, result.values) && shallowEqual(last.accessibility ?? {}, result.accessibility ?? {}) : !result.ok && !last.ok && last.problem === result.problem)) stable.current = result;
|
|
6236
|
+
return stable.current;
|
|
6237
|
+
}
|
|
6238
|
+
const NO_SELECTION = {
|
|
6239
|
+
ok: true,
|
|
6240
|
+
values: {}
|
|
6241
|
+
};
|
|
6242
|
+
function isSameSelection(a, b) {
|
|
6243
|
+
if (a.ok && b.ok) return shallowEqual(a.values, b.values);
|
|
6244
|
+
if (!a.ok && !b.ok) return a.problem === b.problem;
|
|
6245
|
+
return false;
|
|
6246
|
+
}
|
|
6247
|
+
/** Runs every selector of a node. A selector that throws gives a problem. */
|
|
6248
|
+
function select(definition, state, modelProps) {
|
|
6249
|
+
const values = {};
|
|
6250
|
+
const selectors = Object.entries(definition.selectors);
|
|
6251
|
+
for (const [key, selector] of selectors) try {
|
|
6252
|
+
values[key] = selector(state, modelProps);
|
|
6253
|
+
} catch (error) {
|
|
6254
|
+
return {
|
|
6255
|
+
ok: false,
|
|
6256
|
+
problem: `${key}: selector threw: ${error instanceof Error ? error.message : String(error)}`
|
|
6257
|
+
};
|
|
6258
|
+
}
|
|
6259
|
+
return {
|
|
6260
|
+
ok: true,
|
|
6261
|
+
values
|
|
6262
|
+
};
|
|
6263
|
+
}
|
|
6264
|
+
/**
|
|
6265
|
+
* The values of the bound props of a node, read from the app state.
|
|
6266
|
+
*
|
|
6267
|
+
* The selectors run again only when the state object or the parsed model
|
|
6268
|
+
* props change. When the new values are shallow-equal to the last ones, the
|
|
6269
|
+
* last snapshot is kept. So the node re-renders only when a selected value
|
|
6270
|
+
* changes. A selector that returns a new array on each call cannot cause a
|
|
6271
|
+
* render loop: with the same inputs, the cached snapshot comes back.
|
|
6272
|
+
*
|
|
6273
|
+
* A node without bound props does not subscribe to the state.
|
|
6274
|
+
*/
|
|
6275
|
+
function useSelection(manager, definition, modelProps) {
|
|
6276
|
+
const hasSelectors = Object.keys(definition.selectors).length > 0;
|
|
6277
|
+
const cache = (0, react.useRef)(void 0);
|
|
6278
|
+
const getSnapshot = () => {
|
|
6279
|
+
if (!hasSelectors || modelProps === void 0) return NO_SELECTION;
|
|
6280
|
+
const state = manager.getState();
|
|
6281
|
+
const cached = cache.current;
|
|
6282
|
+
if (cached !== void 0 && cached.state === state && cached.modelProps === modelProps) return cached.selection;
|
|
6283
|
+
const next = select(definition, state, modelProps);
|
|
6284
|
+
const selection = cached !== void 0 && isSameSelection(cached.selection, next) ? cached.selection : next;
|
|
6285
|
+
cache.current = {
|
|
6286
|
+
state,
|
|
6287
|
+
modelProps,
|
|
6288
|
+
selection
|
|
6289
|
+
};
|
|
6290
|
+
return selection;
|
|
6291
|
+
};
|
|
6292
|
+
return (0, react.useSyncExternalStore)(hasSelectors ? manager.subscribe : noopSubscribe, getSnapshot, getSnapshot);
|
|
6293
|
+
}
|
|
6294
|
+
/**
|
|
6295
|
+
* Checks the model-set props with the props schema before the selectors run.
|
|
6296
|
+
* The bound props are not set yet, so their issues do not count. Neither do
|
|
6297
|
+
* issues with no path (a check over several props), which the full check
|
|
6298
|
+
* after the selectors reports.
|
|
6299
|
+
*/
|
|
6300
|
+
function checkModelProps(definition, modelData) {
|
|
6301
|
+
if (!definition.props) return {
|
|
6302
|
+
ok: true,
|
|
6303
|
+
values: {}
|
|
6304
|
+
};
|
|
6305
|
+
let values = modelData;
|
|
6306
|
+
let result = validateSchemaSync(definition.props, values);
|
|
6307
|
+
if (!result.success) {
|
|
6308
|
+
const unset = withoutRejectedNulls(values, result.issues);
|
|
6309
|
+
if (unset) {
|
|
6310
|
+
values = unset;
|
|
6311
|
+
result = validateSchemaSync(definition.props, values);
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
if (!result.success) {
|
|
6315
|
+
const issues = result.issues.filter((issue) => {
|
|
6316
|
+
const key = issueKey(issue);
|
|
6317
|
+
return key === void 0 ? isAsyncIssue(issue) : !Object.hasOwn(definition.selectors, key);
|
|
6318
|
+
});
|
|
6319
|
+
if (issues.length > 0) return {
|
|
6320
|
+
ok: false,
|
|
6321
|
+
problem: formatIssues(issues)
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
return {
|
|
6325
|
+
ok: true,
|
|
6326
|
+
values
|
|
6327
|
+
};
|
|
6328
|
+
}
|
|
6329
|
+
/**
|
|
6330
|
+
* The data props of a node, in three steps: check the model-set props with
|
|
6331
|
+
* the props schema, run the selectors with them, then parse all props with
|
|
6332
|
+
* the props schema. Gives the reason when a step fails.
|
|
6333
|
+
*/
|
|
6334
|
+
function useDataProps(runtime, surface, definition, properties, basePath, overrides) {
|
|
6335
|
+
const modelData = useModelDataProps(surface, definition, properties, basePath, overrides);
|
|
6336
|
+
const modelCheck = (0, react.useMemo)(() => modelData.ok ? checkModelProps(definition, modelData.values) : modelData, [definition, modelData]);
|
|
6337
|
+
const selection = useSelection(runtime.manager, definition, modelCheck.ok ? modelCheck.values : void 0);
|
|
6338
|
+
const accessibility = modelData.ok ? modelData.accessibility : void 0;
|
|
6339
|
+
return (0, react.useMemo)(() => {
|
|
6340
|
+
if (!modelCheck.ok) return modelCheck;
|
|
6341
|
+
if (!selection.ok) return selection;
|
|
6342
|
+
if (!definition.props) return {
|
|
6343
|
+
ok: true,
|
|
6344
|
+
props: {},
|
|
6345
|
+
accessibility
|
|
6346
|
+
};
|
|
6347
|
+
const input = {
|
|
6348
|
+
...modelCheck.values,
|
|
6349
|
+
...selection.values
|
|
6350
|
+
};
|
|
6351
|
+
let parsed = validateSchemaSync(definition.props, input);
|
|
6352
|
+
if (!parsed.success) {
|
|
6353
|
+
const unset = withoutRejectedNulls(input, parsed.issues);
|
|
6354
|
+
if (unset) parsed = validateSchemaSync(definition.props, unset);
|
|
6355
|
+
}
|
|
6356
|
+
if (!parsed.success) return {
|
|
6357
|
+
ok: false,
|
|
6358
|
+
problem: formatIssues(parsed.issues)
|
|
6359
|
+
};
|
|
6360
|
+
return {
|
|
6361
|
+
ok: true,
|
|
6362
|
+
props: parsed.data,
|
|
6363
|
+
accessibility
|
|
6364
|
+
};
|
|
6365
|
+
}, [
|
|
6366
|
+
definition,
|
|
6367
|
+
modelCheck,
|
|
6368
|
+
selection,
|
|
6369
|
+
accessibility
|
|
6370
|
+
]);
|
|
6371
|
+
}
|
|
6372
|
+
/**
|
|
6373
|
+
* The event props of a node. A matched event (one that matches a declared
|
|
6374
|
+
* function) is always bound. An unmatched event is bound only when the model
|
|
6375
|
+
* bound it.
|
|
6376
|
+
*/
|
|
6377
|
+
function useEventProps(runtime, surface, componentId, definition, properties, basePath) {
|
|
6378
|
+
const latest = (0, react.useRef)(properties);
|
|
6379
|
+
latest.current = properties;
|
|
6380
|
+
const boundNames = Object.keys(definition.events ?? {}).filter((name) => Object.hasOwn(definition.eventFunctions, name) || (0, _copilotkit_shared.isActionBinding)(properties[name])).join("|");
|
|
6381
|
+
return (0, react.useMemo)(() => {
|
|
6382
|
+
const handlers = {};
|
|
6383
|
+
const names = boundNames ? boundNames.split("|") : [];
|
|
6384
|
+
for (const name of names) {
|
|
6385
|
+
const call = {
|
|
6386
|
+
runtime,
|
|
6387
|
+
surface,
|
|
6388
|
+
componentId,
|
|
6389
|
+
name,
|
|
6390
|
+
parameters: definition.events?.[name]?.parameters
|
|
6391
|
+
};
|
|
6392
|
+
const functionName = Object.hasOwn(definition.eventFunctions, name) ? definition.eventFunctions[name] : void 0;
|
|
6393
|
+
handlers[name] = functionName === void 0 ? (args) => invokeBoundEvent({
|
|
6394
|
+
...call,
|
|
6395
|
+
binding: latest.current[name],
|
|
6396
|
+
basePath,
|
|
6397
|
+
args
|
|
6398
|
+
}) : (args) => invokeMatchedEvent({
|
|
6399
|
+
...call,
|
|
6400
|
+
functionName,
|
|
6401
|
+
args
|
|
6402
|
+
});
|
|
6403
|
+
}
|
|
6404
|
+
return handlers;
|
|
6405
|
+
}, [
|
|
6406
|
+
boundNames,
|
|
6407
|
+
runtime,
|
|
6408
|
+
surface,
|
|
6409
|
+
componentId,
|
|
6410
|
+
definition,
|
|
6411
|
+
basePath
|
|
6412
|
+
]);
|
|
6413
|
+
}
|
|
6414
|
+
/**
|
|
6415
|
+
* The setter props of a node: `setValue` for the writable prop `value`. For
|
|
6416
|
+
* a prop the model bound to `{"@path"}`, the setter writes the data model
|
|
6417
|
+
* and makes the path user-owned. For a literal prop, it sets `overrides`, a
|
|
6418
|
+
* local value of this mounted node. The setters keep their identity.
|
|
6419
|
+
*/
|
|
6420
|
+
function useSetterProps(runtime, surface, componentId, definition, properties, basePath) {
|
|
6421
|
+
const [overrides, setOverrides] = (0, react.useState)({});
|
|
6422
|
+
const latest = (0, react.useRef)(properties);
|
|
6423
|
+
latest.current = properties;
|
|
6424
|
+
return {
|
|
6425
|
+
overrides,
|
|
6426
|
+
setters: (0, react.useMemo)(() => {
|
|
6427
|
+
const result = {};
|
|
6428
|
+
const writable = definition.writable ?? [];
|
|
6429
|
+
for (const key of writable) result[setterNameOf(key)] = (value) => {
|
|
6430
|
+
const binding = latest.current[key];
|
|
6431
|
+
if (!(0, _copilotkit_shared.isPathBinding)(binding)) {
|
|
6432
|
+
setOverrides((current) => ({
|
|
6433
|
+
...current,
|
|
6434
|
+
[key]: value
|
|
6435
|
+
}));
|
|
6436
|
+
return;
|
|
6437
|
+
}
|
|
6438
|
+
try {
|
|
6439
|
+
runtime.manager.setOwnedValue(surface.key, resolvePath(binding["@path"], basePath), value);
|
|
6440
|
+
} catch (cause) {
|
|
6441
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
6442
|
+
reportErrorSafely(runtime, {
|
|
6443
|
+
kind: "props",
|
|
6444
|
+
surfaceId: surface.surfaceId,
|
|
6445
|
+
componentId,
|
|
6446
|
+
error: /* @__PURE__ */ new Error(`${setterNameOf(key)}: ${message}`)
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
6449
|
+
};
|
|
6450
|
+
return result;
|
|
6451
|
+
}, [
|
|
6452
|
+
runtime,
|
|
6453
|
+
surface,
|
|
6454
|
+
componentId,
|
|
6455
|
+
definition,
|
|
6456
|
+
basePath
|
|
6457
|
+
])
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
6460
|
+
function useSlotProps(surface, definition, properties, componentId, basePath, ancestors) {
|
|
6461
|
+
return (0, react.useMemo)(() => {
|
|
6462
|
+
const childAncestors = [...ancestors, scopedId(componentId, basePath)];
|
|
6463
|
+
const slots = {};
|
|
6464
|
+
for (const [name, kind] of Object.entries(definition.slots ?? {})) {
|
|
6465
|
+
const value = properties[name];
|
|
6466
|
+
if (kind === "single" && typeof value === "string") slots[name] = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6467
|
+
surface,
|
|
6468
|
+
componentId: value,
|
|
6469
|
+
basePath,
|
|
6470
|
+
ancestors: childAncestors
|
|
6471
|
+
});
|
|
6472
|
+
else if (kind === "multiple" && isTemplateChildren(value)) slots[name] = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateChildren, {
|
|
6473
|
+
surface,
|
|
6474
|
+
template: value,
|
|
6475
|
+
basePath,
|
|
6476
|
+
ancestors: childAncestors
|
|
6477
|
+
});
|
|
6478
|
+
else if (kind === "multiple" && Array.isArray(value)) slots[name] = [...new Set(value.filter((id) => typeof id === "string"))].map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6479
|
+
surface,
|
|
6480
|
+
componentId: id,
|
|
6481
|
+
basePath,
|
|
6482
|
+
ancestors: childAncestors
|
|
6483
|
+
}, id));
|
|
6484
|
+
}
|
|
6485
|
+
return slots;
|
|
6486
|
+
}, [
|
|
6487
|
+
surface,
|
|
6488
|
+
definition,
|
|
6489
|
+
properties,
|
|
6490
|
+
componentId,
|
|
6491
|
+
basePath,
|
|
6492
|
+
ancestors
|
|
6493
|
+
]);
|
|
6494
|
+
}
|
|
6495
|
+
/**
|
|
6496
|
+
* The parsed data props with every typed child reference (`child()`,
|
|
6497
|
+
* `childList()`, at any depth) replaced by the rendered component. Without
|
|
6498
|
+
* references, the parsed props come back as they are.
|
|
6499
|
+
*/
|
|
6500
|
+
function useChildRefProps(surface, refs, props, componentId, basePath, ancestors) {
|
|
6501
|
+
return (0, react.useMemo)(() => {
|
|
6502
|
+
if (props === void 0 || refs.length === 0) return props;
|
|
6503
|
+
const childAncestors = [...ancestors, scopedId(componentId, basePath)];
|
|
6504
|
+
const render = (kind, value) => {
|
|
6505
|
+
if (kind === "single") return typeof value === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6506
|
+
surface,
|
|
6507
|
+
componentId: value,
|
|
6508
|
+
basePath,
|
|
6509
|
+
ancestors: childAncestors
|
|
6510
|
+
}) : void 0;
|
|
6511
|
+
if (isTemplateChildren(value)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateChildren, {
|
|
6512
|
+
surface,
|
|
6513
|
+
template: value,
|
|
6514
|
+
basePath,
|
|
6515
|
+
ancestors: childAncestors
|
|
6516
|
+
});
|
|
6517
|
+
if (!Array.isArray(value)) return void 0;
|
|
6518
|
+
return [...new Set(value.filter((id) => typeof id === "string"))].map((id) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeView, {
|
|
6519
|
+
surface,
|
|
6520
|
+
componentId: id,
|
|
6521
|
+
basePath,
|
|
6522
|
+
ancestors: childAncestors
|
|
6523
|
+
}, id));
|
|
6524
|
+
};
|
|
6525
|
+
let result = props;
|
|
6526
|
+
for (const ref of refs) result = (0, _copilotkit_shared.mapAtChildRef)(result, ref.path, (found) => render(ref.kind, found));
|
|
6527
|
+
return result;
|
|
6528
|
+
}, [
|
|
6529
|
+
surface,
|
|
6530
|
+
refs,
|
|
6531
|
+
props,
|
|
6532
|
+
componentId,
|
|
6533
|
+
basePath,
|
|
6534
|
+
ancestors
|
|
6535
|
+
]);
|
|
6536
|
+
}
|
|
6537
|
+
function ResolvedNode({ surface, componentId, definition, properties, runtime, basePath, ancestors }) {
|
|
6538
|
+
const { overrides, setters } = useSetterProps(runtime, surface, componentId, definition, properties, basePath);
|
|
6539
|
+
const dataProps = useDataProps(runtime, surface, definition, properties, basePath, overrides);
|
|
6540
|
+
const eventProps = useEventProps(runtime, surface, componentId, definition, properties, basePath);
|
|
6541
|
+
const slotProps = useSlotProps(surface, definition, properties, componentId, basePath, ancestors);
|
|
6542
|
+
const childRefProps = useChildRefProps(surface, definition.childRefs, dataProps.ok ? dataProps.props : void 0, componentId, basePath, ancestors);
|
|
6543
|
+
const accessibility = dataProps.ok ? dataProps.accessibility : void 0;
|
|
6544
|
+
const nodeContext = (0, react.useMemo)(() => ({
|
|
6545
|
+
surfaceKey: surface.key,
|
|
6546
|
+
surfaceId: surface.surfaceId,
|
|
6547
|
+
componentId,
|
|
6548
|
+
accessibility
|
|
6549
|
+
}), [
|
|
6550
|
+
surface.key,
|
|
6551
|
+
surface.surfaceId,
|
|
6552
|
+
componentId,
|
|
6553
|
+
accessibility
|
|
6554
|
+
]);
|
|
6555
|
+
const problem = dataProps.ok ? void 0 : dataProps.problem;
|
|
6556
|
+
(0, react.useEffect)(() => {
|
|
6557
|
+
if (problem) reportErrorSafely(runtime, {
|
|
6558
|
+
kind: "props",
|
|
6559
|
+
surfaceId: surface.surfaceId,
|
|
6560
|
+
componentId,
|
|
6561
|
+
error: /* @__PURE__ */ new Error(`Invalid props: ${problem}`)
|
|
6562
|
+
});
|
|
6563
|
+
}, [
|
|
6564
|
+
problem,
|
|
6565
|
+
runtime,
|
|
6566
|
+
surface.surfaceId,
|
|
6567
|
+
componentId
|
|
6568
|
+
]);
|
|
6569
|
+
if (!dataProps.ok) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultNodeFallback, {
|
|
6570
|
+
message: `invalid props (${problem})`,
|
|
6571
|
+
componentId
|
|
6572
|
+
});
|
|
6573
|
+
const Component = definition.component;
|
|
6574
|
+
const rendered = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
6575
|
+
...childRefProps,
|
|
6576
|
+
...eventProps,
|
|
6577
|
+
...slotProps,
|
|
6578
|
+
...setters
|
|
6579
|
+
});
|
|
6580
|
+
const hasEnvelope = properties.accessibility !== void 0 && definition.accessibility !== "self";
|
|
6581
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenerativeUINodeContext.Provider, {
|
|
6582
|
+
value: nodeContext,
|
|
6583
|
+
children: hasEnvelope ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6584
|
+
"data-a2ui-accessibility": "",
|
|
6585
|
+
style: ROOT_STYLE,
|
|
6586
|
+
...wrapperAria(accessibilityAttributes(accessibility)),
|
|
6587
|
+
children: rendered
|
|
6588
|
+
}) : rendered
|
|
6589
|
+
});
|
|
6590
|
+
}
|
|
6591
|
+
|
|
6592
|
+
//#endregion
|
|
6593
|
+
//#region src/v2/generative-ui/chat.tsx
|
|
6594
|
+
const GenerativeUIChatSlotContext = (0, react.createContext)(void 0);
|
|
6595
|
+
function DefaultGenerativeUIChatCard({ surfaceId, isStreaming, error, children }) {
|
|
6596
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6597
|
+
"data-generative-ui-chat-surface": surfaceId,
|
|
6598
|
+
className: "cpk:my-2 cpk:w-full cpk:rounded-xl cpk:border cpk:border-gray-200 cpk:p-3",
|
|
6599
|
+
children: [children, error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6600
|
+
role: "alert",
|
|
6601
|
+
className: "cpk:mt-2 cpk:text-xs cpk:text-red-600",
|
|
6602
|
+
children: "Part of this UI couldn't be shown."
|
|
6603
|
+
})]
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
6606
|
+
const ContentSchema = zod.z.object({
|
|
6607
|
+
surfaceId: zod.z.string(),
|
|
6608
|
+
target: zod.z.string(),
|
|
6609
|
+
catalogId: zod.z.string(),
|
|
6610
|
+
operations: zod.z.array(zod.z.any()),
|
|
6611
|
+
status: zod.z.enum([
|
|
6612
|
+
"streaming",
|
|
6613
|
+
"ready",
|
|
6614
|
+
"error"
|
|
6615
|
+
]),
|
|
6616
|
+
rejected: zod.z.array(zod.z.object({
|
|
6617
|
+
index: zod.z.number(),
|
|
6618
|
+
reason: zod.z.string()
|
|
6619
|
+
})).default([])
|
|
6620
|
+
}).passthrough();
|
|
6621
|
+
function GenerativeUIChatActivity({ content, agent }) {
|
|
6622
|
+
const runtime = useGenerativeUIRuntime();
|
|
6623
|
+
const threadId = useCopilotChatConfiguration()?.threadId ?? agent?.threadId;
|
|
6624
|
+
const Wrapper = (0, react.useContext)(GenerativeUIChatSlotContext) ?? DefaultGenerativeUIChatCard;
|
|
6625
|
+
useManagerVersion(runtime);
|
|
6626
|
+
(0, react.useLayoutEffect)(() => {
|
|
6627
|
+
if (runtime && threadId) runtime.manager.ingest(threadId, agent?.agentId, content);
|
|
6628
|
+
}, [
|
|
6629
|
+
runtime,
|
|
6630
|
+
threadId,
|
|
6631
|
+
agent?.agentId,
|
|
6632
|
+
content
|
|
6633
|
+
]);
|
|
6634
|
+
if (!runtime || !threadId || content.target !== _copilotkit_shared.GENERATIVE_UI_CHAT_TARGET) return null;
|
|
6635
|
+
const surface = runtime.manager.getSurface(threadId, content.surfaceId) ?? runtime.manager.getFailedSurface(threadId, content.surfaceId);
|
|
6636
|
+
if (!surface) return null;
|
|
6637
|
+
const rejected = [...content.rejected ?? [], ...surface.clientRejected];
|
|
6638
|
+
const error = rejected.length ? new Error(`${rejected.length} UI update(s) could not be shown`, { cause: rejected }) : void 0;
|
|
6639
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, {
|
|
6640
|
+
surfaceId: content.surfaceId,
|
|
6641
|
+
isStreaming: content.status === "streaming",
|
|
6642
|
+
error,
|
|
6643
|
+
rejected,
|
|
6644
|
+
children: surface.failed ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceView, { surface })
|
|
6645
|
+
});
|
|
6646
|
+
}
|
|
6647
|
+
function createGenerativeUIActivityRenderer() {
|
|
6648
|
+
return {
|
|
6649
|
+
activityType: _copilotkit_shared.GENERATIVE_UI_ACTIVITY_TYPE,
|
|
6650
|
+
content: ContentSchema,
|
|
6651
|
+
render: GenerativeUIChatActivity
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6654
|
+
/**
|
|
6655
|
+
* Shows nothing in the chat for an agent-called function. The root reads the
|
|
6656
|
+
* function-call activities from the messages and runs them. This renderer
|
|
6657
|
+
* only keeps a wildcard ("*") activity renderer from showing them.
|
|
6658
|
+
*/
|
|
6659
|
+
function createGenerativeUIFunctionCallActivityRenderer() {
|
|
6660
|
+
return {
|
|
6661
|
+
activityType: _copilotkit_shared.GENERATIVE_UI_FUNCTION_CALL_ACTIVITY_TYPE,
|
|
6662
|
+
content: zod.z.unknown(),
|
|
6663
|
+
render: () => null
|
|
6664
|
+
};
|
|
6665
|
+
}
|
|
6666
|
+
/** Hides the raw `render_ui` tool call; the surface activity owns the UX. */
|
|
6667
|
+
function GenerativeUIToolCallRenderer() {
|
|
6668
|
+
const { copilotkit } = useCopilotKit();
|
|
6669
|
+
(0, react.useEffect)(() => {
|
|
6670
|
+
const renderer = defineToolCallRenderer({
|
|
6671
|
+
name: _copilotkit_shared.GENERATIVE_UI_RENDER_TOOL,
|
|
6672
|
+
args: zod.z.any(),
|
|
6673
|
+
render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
|
|
6674
|
+
});
|
|
6675
|
+
const existing = copilotkit._renderToolCalls ?? [];
|
|
6676
|
+
copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== _copilotkit_shared.GENERATIVE_UI_RENDER_TOOL), renderer]);
|
|
6677
|
+
}, [copilotkit]);
|
|
6678
|
+
return null;
|
|
6679
|
+
}
|
|
6680
|
+
|
|
2226
6681
|
//#endregion
|
|
2227
6682
|
//#region src/v2/providers/CopilotKitProvider.tsx
|
|
2228
6683
|
const zodToJsonSchemaAdapter = (schema, options) => {
|
|
@@ -2269,7 +6724,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2269
6724
|
return value;
|
|
2270
6725
|
}
|
|
2271
6726
|
/** Provide a shared core instance and synchronize runtime, tool, and UI configuration. */
|
|
2272
|
-
const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, messageFilter, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, showIntelligenceIndicator = true, useSingleEndpoint, onError, a2ui, mcpApps, defaultThrottleMs, debug, learning }) => {
|
|
6727
|
+
const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, messageFilter, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, showIntelligenceIndicator = true, useSingleEndpoint, onError, a2ui, generativeUI, mcpApps, defaultThrottleMs, debug, learning }) => {
|
|
2273
6728
|
const learningConfig = (0, react.useMemo)(() => toLearningConfig(learning), [learning]);
|
|
2274
6729
|
const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
|
|
2275
6730
|
const [inspectorVisible, setInspectorVisible] = (0, react.useState)(false);
|
|
@@ -2291,6 +6746,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2291
6746
|
const openGenUIActive = runtimeOpenGenUIEnabled || !!openGenerativeUI;
|
|
2292
6747
|
const a2uiCatalogProvided = !!a2ui?.catalog;
|
|
2293
6748
|
const a2uiActive = runtimeA2UIEnabled || a2uiCatalogProvided;
|
|
6749
|
+
const generativeUIActive = !!generativeUI?.ui;
|
|
2294
6750
|
const [runtimeLicenseStatus, setRuntimeLicenseStatus] = (0, react.useState)(void 0);
|
|
2295
6751
|
const [runtimeEntitlements, setRuntimeEntitlements] = (0, react.useState)(void 0);
|
|
2296
6752
|
const [runtimeEntitlementRetryPending, setRuntimeEntitlementRetryPending] = (0, react.useState)(false);
|
|
@@ -2344,10 +6800,12 @@ react_markdown = __toESM(react_markdown);
|
|
|
2344
6800
|
loadingComponent: a2ui?.loadingComponent,
|
|
2345
6801
|
recovery: a2ui?.recovery
|
|
2346
6802
|
}));
|
|
6803
|
+
if (generativeUIActive) renderers.push(createGenerativeUIActivityRenderer(), createGenerativeUIFunctionCallActivityRenderer());
|
|
2347
6804
|
return renderers;
|
|
2348
6805
|
}, [
|
|
2349
6806
|
a2uiActive,
|
|
2350
6807
|
openGenUIActive,
|
|
6808
|
+
generativeUIActive,
|
|
2351
6809
|
a2ui,
|
|
2352
6810
|
filteredCatalog,
|
|
2353
6811
|
mcpAppsAllowSameOrigin
|
|
@@ -2514,6 +6972,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2514
6972
|
if (defaultThrottleMs !== void 0) copilotkitRef.current.setDefaultThrottleMs(defaultThrottleMs);
|
|
2515
6973
|
}
|
|
2516
6974
|
const copilotkit = copilotkitRef.current;
|
|
6975
|
+
const generativeUIRuntime = useGenerativeUIRoot(copilotkit, generativeUI);
|
|
2517
6976
|
(0, react.useEffect)(() => {
|
|
2518
6977
|
if (!rawCatalog) return;
|
|
2519
6978
|
if (!(rawCatalog instanceof _copilotkit_a2ui_renderer.Catalog)) return;
|
|
@@ -2752,6 +7211,7 @@ react_markdown = __toESM(react_markdown);
|
|
|
2752
7211
|
value: licenseContextValue,
|
|
2753
7212
|
children: [
|
|
2754
7213
|
a2uiActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIBuiltInToolCallRenderer, {}),
|
|
7214
|
+
generativeUIActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenerativeUIToolCallRenderer, {}),
|
|
2755
7215
|
a2uiActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UICatalogContext, {
|
|
2756
7216
|
catalog: filteredCatalog,
|
|
2757
7217
|
includeSchema: a2ui?.includeSchema
|
|
@@ -2760,7 +7220,10 @@ react_markdown = __toESM(react_markdown);
|
|
|
2760
7220
|
value: inspectorContextValue,
|
|
2761
7221
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitAgentIdContext.Provider, {
|
|
2762
7222
|
value: agentId,
|
|
2763
|
-
children
|
|
7223
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenerativeUIRuntimeContext.Provider, {
|
|
7224
|
+
value: generativeUIRuntime,
|
|
7225
|
+
children
|
|
7226
|
+
})
|
|
2764
7227
|
}), shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, {
|
|
2765
7228
|
core: copilotkit,
|
|
2766
7229
|
openRequest: inspectorOpenRequest,
|