@datalayer/agent-runtimes 1.3.113 → 1.4.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-embed/chunks/{ChatView-D41Nx5rC.js → ChatView-BpKZPdcp.js} +4 -4
- package/dist-embed/datalayer-app-main.js +150 -150
- package/lib/apps/apps/appspec.d.ts +13 -0
- package/lib/apps/apps/appspec.js +125 -0
- package/lib/apps/apps/checks.d.ts +1 -1
- package/lib/apps/apps/checks.js +2 -2
- package/lib/apps/apps/customComponents.js +1 -1
- package/lib/apps/core/canvasBlocks.d.ts +3 -3
- package/lib/apps/core/canvasParts.d.ts +6 -4
- package/lib/apps/embed/AppEmbed.js +3 -7
- package/lib/apps/embed/embedSession.d.ts +8 -4
- package/lib/apps/embed/embedSession.js +16 -9
- package/lib/apps/plugins/a2ui-surface/AnswerShows.d.ts +21 -0
- package/lib/apps/plugins/a2ui-surface/AnswerShows.js +29 -0
- package/lib/apps/plugins/a2ui-surface/SurfaceToolResult.js +4 -7
- package/lib/apps/plugins/a2ui-surface/index.d.ts +2 -1
- package/lib/apps/plugins/a2ui-surface/index.js +10 -1
- package/lib/apps/plugins/a2ui-surface/toolResult.d.ts +20 -11
- package/lib/apps/plugins/a2ui-surface/toolResult.js +27 -11
- package/lib/apps/plugins/app-components/index.d.ts +1 -1
- package/lib/apps/plugins/app-components/index.js +2 -2
- package/lib/apps/plugins/app-elements/AppElements.d.ts +3 -3
- package/lib/apps/plugins/app-elements/AppElements.js +5 -20
- package/lib/apps/plugins/app-elements/QuestionView.d.ts +28 -0
- package/lib/apps/plugins/app-elements/QuestionView.js +57 -0
- package/lib/apps/plugins/app-elements/index.d.ts +7 -4
- package/lib/apps/plugins/app-elements/index.js +14 -5
- package/lib/apps/plugins/canvas-blocks/index.d.ts +13 -13
- package/lib/apps/plugins/canvas-blocks/index.js +24 -24
- package/lib/apps/plugins/canvas-parts/index.d.ts +7 -1
- package/lib/apps/plugins/canvas-parts/index.js +36 -0
- package/lib/apps/plugins/chat/ChatView.js +7 -0
- package/lib/chat/base/ChatBase.js +234 -43
- package/lib/chat/base/loopElement.d.ts +16 -17
- package/lib/chat/base/loopElement.js +11 -27
- package/lib/chat/base/loopStep.d.ts +15 -15
- package/lib/chat/base/loopStep.js +10 -13
- package/lib/chat/base/loopWindow.d.ts +15 -20
- package/lib/chat/base/loopWindow.js +26 -27
- package/lib/chat/base/uiOps.d.ts +96 -0
- package/lib/chat/base/uiOps.js +251 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +4 -4
- package/lib/components/a2ui/datalayer/Chart.d.ts +1 -1
- package/lib/components/a2ui/datalayer/schema.js +1 -1
- package/lib/components/teams/AnswerSurfaces.d.ts +3 -2
- package/lib/components/teams/AnswerSurfaces.js +7 -7
- package/lib/components/teams/useA2ATeam.d.ts +1 -1
- package/lib/config/AgentConfiguration.d.ts +1 -1
- package/lib/protocols/AGUIAdapter.d.ts +7 -0
- package/lib/protocols/AGUIAdapter.js +43 -3
- package/lib/protocols/agentic-commerce/index.d.ts +5 -9
- package/lib/protocols/agentic-commerce/index.js +6 -10
- package/lib/protocols/commerce.d.ts +8 -9
- package/lib/protocols/commerce.js +29 -0
- package/lib/protocols/ucp/index.d.ts +5 -10
- package/lib/protocols/ucp/index.js +6 -11
- package/lib/specs/actions.d.ts +8 -0
- package/lib/specs/actions.js +56 -0
- package/lib/specs/agents/agents.js +338 -169
- package/lib/specs/apps.d.ts +1 -0
- package/lib/specs/apps.js +272 -0
- package/lib/specs/appspecSchema.js +125 -0
- package/lib/specs/cogs.js +6 -3
- package/lib/specs/index.d.ts +2 -1
- package/lib/specs/index.js +2 -1
- package/lib/specs/protocols.d.ts +24 -0
- package/lib/specs/protocols.js +197 -0
- package/lib/specs/ui.d.ts +21 -0
- package/lib/specs/{uiPlugins.js → ui.js} +14 -14
- package/lib/types/agentspecs.d.ts +72 -5
- package/lib/types/messages.d.ts +7 -0
- package/lib/types/protocol.d.ts +8 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_agents.py +43 -8
- package/scripts/codegen/generate_apps.py +38 -1
- package/scripts/codegen/generate_protocols.py +207 -0
- package/scripts/codegen/generate_sandbox_agents.py +1 -1
- package/scripts/codegen/{generate_ui_plugins.py → generate_ui.py} +40 -40
- package/scripts/gen_workers.py +2 -2
- package/lib/chat/base/loopMessage.d.ts +0 -37
- package/lib/chat/base/loopMessage.js +0 -37
- package/lib/specs/uiPlugins.d.ts +0 -21
|
@@ -87,6 +87,19 @@ export interface ParsedAppspec {
|
|
|
87
87
|
* is always whole, so an editor can open it and show what is wrong.
|
|
88
88
|
*/
|
|
89
89
|
export declare function parseAppspec(document: unknown): ParsedAppspec;
|
|
90
|
+
/**
|
|
91
|
+
* What is wrong with the UI an application says its answers render in
|
|
92
|
+
* (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
|
|
93
|
+
* know or said twice, one not enabled while it is offered.
|
|
94
|
+
*/
|
|
95
|
+
export declare function uiProblems(app: Pick<AppSpec, 'ui' | 'enabled'>): string[];
|
|
96
|
+
/**
|
|
97
|
+
* What is wrong with the protocols an application says it speaks
|
|
98
|
+
* (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
|
|
99
|
+
* know or said twice, a planned protocol while it is offered, and a `pap`,
|
|
100
|
+
* `pact` or `a2a` block configuring a protocol it does not list.
|
|
101
|
+
*/
|
|
102
|
+
export declare function protocolsProblems(app: Pick<AppSpec, 'protocols' | 'enabled' | 'pap' | 'pact' | 'a2a'>): string[];
|
|
90
103
|
/**
|
|
91
104
|
* An application as the document its file holds.
|
|
92
105
|
*
|
package/lib/apps/apps/appspec.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
import { BEHAVIOURS } from './rules';
|
|
6
|
+
import { PROTOCOL_CATALOGUE } from '../../specs/protocols';
|
|
7
|
+
import { UI_CATALOGUE } from '../../specs/ui';
|
|
6
8
|
import { DEFAULT_MAX_FILES, DEFAULT_UPLOAD_MB } from './uploads';
|
|
7
9
|
/** The version of the spec itself. */
|
|
8
10
|
export const APP_SCHEMA = 'loop.app/v1';
|
|
@@ -130,6 +132,8 @@ export function emptyAppspec(kind = 'chat') {
|
|
|
130
132
|
context: [],
|
|
131
133
|
contents: [],
|
|
132
134
|
connections: [],
|
|
135
|
+
protocols: [],
|
|
136
|
+
ui: [],
|
|
133
137
|
rules: [],
|
|
134
138
|
permissions: {
|
|
135
139
|
spaces: [],
|
|
@@ -195,7 +199,11 @@ const KNOWN_KEYS = [
|
|
|
195
199
|
'context',
|
|
196
200
|
'contents',
|
|
197
201
|
'connections',
|
|
202
|
+
'protocols',
|
|
203
|
+
'ui',
|
|
198
204
|
'pap',
|
|
205
|
+
'pact',
|
|
206
|
+
'a2a',
|
|
199
207
|
'rules',
|
|
200
208
|
'permissions',
|
|
201
209
|
'interface',
|
|
@@ -312,6 +320,50 @@ function dumpPap(pap) {
|
|
|
312
320
|
.text('summary', operation.summary, '\u0000')
|
|
313
321
|
.value('user_approval_required', operation.userApprovalRequired, true).data)).data).data;
|
|
314
322
|
}
|
|
323
|
+
/** PACT (AGENT_PROTOCOLS PI-01): the businesses it asks for each person. */
|
|
324
|
+
function parsePact(data) {
|
|
325
|
+
return {
|
|
326
|
+
connections: records(data.connections).map(connection => ({
|
|
327
|
+
id: text(connection.id),
|
|
328
|
+
brand: text(connection.brand),
|
|
329
|
+
audience: text(connection.audience),
|
|
330
|
+
access: oneOf(connection.access, ['read', 'write'], 'read'),
|
|
331
|
+
scopes: texts(connection.scopes),
|
|
332
|
+
})),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/** PACT as its document says it, nothing at its default. */
|
|
336
|
+
function dumpPact(pact) {
|
|
337
|
+
if (!pact)
|
|
338
|
+
return {};
|
|
339
|
+
return new Writer().list('connections', pact.connections.map(connection => new Writer()
|
|
340
|
+
.text('id', connection.id, '\u0000')
|
|
341
|
+
.text('brand', connection.brand, '\u0000')
|
|
342
|
+
.text('audience', connection.audience, '\u0000')
|
|
343
|
+
.value('access', connection.access, 'read')
|
|
344
|
+
.list('scopes', connection.scopes).data)).data;
|
|
345
|
+
}
|
|
346
|
+
/** A2A (AGENT_PROTOCOLS PR-02): the extensions its Agent Card advertises. */
|
|
347
|
+
function parseA2A(data) {
|
|
348
|
+
return {
|
|
349
|
+
extensions: records(data.extensions).map(extension => ({
|
|
350
|
+
uri: text(extension.uri),
|
|
351
|
+
description: text(extension.description),
|
|
352
|
+
required: flag(extension.required, false),
|
|
353
|
+
params: isData(extension.params) ? { ...extension.params } : {},
|
|
354
|
+
})),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/** A2A as its document says it, nothing at its default. */
|
|
358
|
+
function dumpA2A(a2a) {
|
|
359
|
+
if (!a2a)
|
|
360
|
+
return {};
|
|
361
|
+
return new Writer().list('extensions', a2a.extensions.map(extension => new Writer()
|
|
362
|
+
.text('uri', extension.uri, '\u0000')
|
|
363
|
+
.text('description', extension.description)
|
|
364
|
+
.value('required', extension.required, false)
|
|
365
|
+
.part('params', extension.params).data)).data;
|
|
366
|
+
}
|
|
315
367
|
function parseRule(data) {
|
|
316
368
|
return {
|
|
317
369
|
action: text(data.action),
|
|
@@ -763,6 +815,8 @@ export function parseAppspec(document) {
|
|
|
763
815
|
context: texts(data.context),
|
|
764
816
|
contents: texts(data.contents),
|
|
765
817
|
connections: records(data.connections).map(parseConnection),
|
|
818
|
+
protocols: texts(data.protocols),
|
|
819
|
+
ui: texts(data.ui),
|
|
766
820
|
rules: records(data.rules).map(parseRule),
|
|
767
821
|
permissions: {
|
|
768
822
|
spaces: records(permissions.spaces).map(grant => ({
|
|
@@ -842,8 +896,75 @@ export function parseAppspec(document) {
|
|
|
842
896
|
if (isData(data.pap)) {
|
|
843
897
|
app.pap = parsePap(data.pap);
|
|
844
898
|
}
|
|
899
|
+
if (isData(data.pact)) {
|
|
900
|
+
app.pact = parsePact(data.pact);
|
|
901
|
+
}
|
|
902
|
+
if (isData(data.a2a)) {
|
|
903
|
+
app.a2a = parseA2A(data.a2a);
|
|
904
|
+
}
|
|
905
|
+
problems.push(...protocolsProblems(app), ...uiProblems(app));
|
|
845
906
|
return { app, problems };
|
|
846
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* What is wrong with the UI an application says its answers render in
|
|
910
|
+
* (AGENT_PROTOCOLS PS-04), as agentspecs says it: an id the catalogue does not
|
|
911
|
+
* know or said twice, one not enabled while it is offered.
|
|
912
|
+
*/
|
|
913
|
+
export function uiProblems(app) {
|
|
914
|
+
const problems = [];
|
|
915
|
+
const seen = [];
|
|
916
|
+
for (const id of app.ui) {
|
|
917
|
+
if (seen.includes(id)) {
|
|
918
|
+
problems.push(`It lists the UI '${id}' twice.`);
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
seen.push(id);
|
|
922
|
+
const spec = UI_CATALOGUE[id];
|
|
923
|
+
if (!spec) {
|
|
924
|
+
problems.push(`There is no UI named '${id}': one of ${Object.keys(UI_CATALOGUE).sort().join(', ')}.`);
|
|
925
|
+
}
|
|
926
|
+
else if (app.enabled && !spec.enabled) {
|
|
927
|
+
problems.push(`The UI ${spec.name} ('${id}') is not enabled: an application cannot render in it yet.`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return problems;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* What is wrong with the protocols an application says it speaks
|
|
934
|
+
* (AGENT_PROTOCOLS PS-02), as agentspecs says it: an id the catalogue does not
|
|
935
|
+
* know or said twice, a planned protocol while it is offered, and a `pap`,
|
|
936
|
+
* `pact` or `a2a` block configuring a protocol it does not list.
|
|
937
|
+
*/
|
|
938
|
+
export function protocolsProblems(app) {
|
|
939
|
+
const problems = [];
|
|
940
|
+
const seen = [];
|
|
941
|
+
for (const id of app.protocols) {
|
|
942
|
+
if (seen.includes(id)) {
|
|
943
|
+
problems.push(`It lists the protocol '${id}' twice.`);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
seen.push(id);
|
|
947
|
+
const spec = PROTOCOL_CATALOGUE[id];
|
|
948
|
+
if (!spec) {
|
|
949
|
+
problems.push(`There is no protocol named '${id}': one of ${Object.keys(PROTOCOL_CATALOGUE).sort().join(', ')}.`);
|
|
950
|
+
}
|
|
951
|
+
else if (app.enabled && spec.status === 'planned') {
|
|
952
|
+
problems.push(`${spec.name} ('${id}') is planned, not shipped: an application cannot speak it yet.`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const configured = {
|
|
956
|
+
pap: Boolean(app.pap && (app.pap.connections.length > 0 || app.pap.company.enabled)),
|
|
957
|
+
pact: Boolean(app.pact && app.pact.connections.length > 0),
|
|
958
|
+
a2a: Boolean(app.a2a && app.a2a.extensions.length > 0),
|
|
959
|
+
};
|
|
960
|
+
for (const spec of Object.values(PROTOCOL_CATALOGUE)) {
|
|
961
|
+
const block = spec.appspecBlock;
|
|
962
|
+
if (block && configured[block] && !app.protocols.includes(spec.id)) {
|
|
963
|
+
problems.push(`Its \`${block}\` block configures ${spec.name}, which it does not list: add '${spec.id}' to \`protocols\`.`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return problems;
|
|
967
|
+
}
|
|
847
968
|
// --- writing --------------------------------------------------------------------------
|
|
848
969
|
const sameList = (a, b) => a.length === b.length && a.every((item, index) => item === b[index]);
|
|
849
970
|
/** A document with a key only when its value says something. */
|
|
@@ -1096,7 +1217,11 @@ export function dumpAppspec(app) {
|
|
|
1096
1217
|
.value('access', connection.access, 'read')
|
|
1097
1218
|
.value('as', connection.as, 'owner')
|
|
1098
1219
|
.list('only', connection.only).data))
|
|
1220
|
+
.list('protocols', app.protocols)
|
|
1221
|
+
.list('ui', app.ui)
|
|
1099
1222
|
.part('pap', dumpPap(app.pap))
|
|
1223
|
+
.part('pact', dumpPact(app.pact))
|
|
1224
|
+
.part('a2a', dumpA2A(app.a2a))
|
|
1100
1225
|
.list('rules', app.rules.map(rule => ({
|
|
1101
1226
|
action: rule.action,
|
|
1102
1227
|
// One target is written alone when it is a class, as a person would.
|
|
@@ -11,7 +11,7 @@ export interface AppCheck {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* The component a layout names (LOOP C-13), from the catalogs of the enabled
|
|
14
|
-
* UI
|
|
14
|
+
* UI specs, by the name a surface gives it.
|
|
15
15
|
*/
|
|
16
16
|
export declare const componentNamed: (name: string) => ComponentSpec | undefined;
|
|
17
17
|
/**
|
package/lib/apps/apps/checks.js
CHANGED
|
@@ -29,7 +29,7 @@ import { getCog } from '../../specs/cogs';
|
|
|
29
29
|
import { getFrame } from '../../specs/frames';
|
|
30
30
|
import { getGate } from '../../specs/gates';
|
|
31
31
|
import { GUARD_CATALOGUE } from '../../specs/guards';
|
|
32
|
-
import { getComponent } from '../../specs/
|
|
32
|
+
import { getComponent } from '../../specs/ui';
|
|
33
33
|
import { MCP_SERVER_LIBRARY } from '../../specs/mcpServers';
|
|
34
34
|
import { getMemory } from '../../specs/memory';
|
|
35
35
|
import { getModel } from '../../specs/models';
|
|
@@ -57,7 +57,7 @@ const idOf = (ref) => {
|
|
|
57
57
|
};
|
|
58
58
|
/**
|
|
59
59
|
* The component a layout names (LOOP C-13), from the catalogs of the enabled
|
|
60
|
-
* UI
|
|
60
|
+
* UI specs, by the name a surface gives it.
|
|
61
61
|
*/
|
|
62
62
|
export const componentNamed = (name) => getComponent(name);
|
|
63
63
|
const own = (record, key) => Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module apps/apps/customComponents
|
|
18
18
|
*/
|
|
19
|
-
import { getComponent } from '../../specs/
|
|
19
|
+
import { getComponent } from '../../specs/ui';
|
|
20
20
|
/** Its name: a word starting with a capital letter, as the catalog's are named. */
|
|
21
21
|
export const CUSTOM_COMPONENT_NAME = /^[A-Z][A-Za-z0-9]{0,63}$/;
|
|
22
22
|
/** What one of its properties may be typed: what the renderer tells apart. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ComponentSpec } from '../../types/agentspecs';
|
|
2
2
|
/**
|
|
3
3
|
* A block the Canvas may place (LOOP C-12): one component of the catalog of
|
|
4
|
-
* visual components (C-13), contributed by the plugin of the UI
|
|
4
|
+
* visual components (C-13), contributed by the plugin of the UI that
|
|
5
5
|
* renders it. The Canvas's palette is what the enabled plugins contribute
|
|
6
6
|
* here, and nothing else: a plugin disabled takes its blocks off it, and an
|
|
7
7
|
* application that uses one of them says so in its setup notes.
|
|
@@ -9,8 +9,8 @@ import type { ComponentSpec } from '../../types/agentspecs';
|
|
|
9
9
|
export type CanvasBlockContribution = {
|
|
10
10
|
/** The name a surface gives it, the component's id (e.g. 'Table'). */
|
|
11
11
|
id: string;
|
|
12
|
-
/** The UI
|
|
13
|
-
|
|
12
|
+
/** The UI of the catalogue that renders it (e.g. 'a2ui'). */
|
|
13
|
+
ui: string;
|
|
14
14
|
component: ComponentSpec;
|
|
15
15
|
};
|
|
16
16
|
export declare const LoopCanvasBlock: import("@datalayer/reactor").ContributionPoint<CanvasBlockContribution>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** The kinds of part an agent is built from on the Canvas. */
|
|
2
|
-
export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model';
|
|
2
|
+
export type CanvasPartKind = 'connection' | 'rule' | 'skill' | 'test' | 'event' | 'model' | 'protocol' | 'ui' | 'character';
|
|
3
3
|
/**
|
|
4
4
|
* A part of an agent the Canvas may place (LOOP C-20): a connection to a
|
|
5
|
-
* server of the catalogue, a skill, a model, a rule, a test, an event
|
|
5
|
+
* server of the catalogue, a skill, a model, a rule, a test, an event, a
|
|
6
|
+
* protocol it speaks, a UI its answers render in, the character it shows
|
|
7
|
+
* (AGENT_PROTOCOLS PS-04, PS-05) —
|
|
6
8
|
* contributed by the plugin of what it comes from. The toolbox is what the
|
|
7
9
|
* enabled plugins contribute here, and nothing else: a plugin disabled takes
|
|
8
10
|
* its parts off it, as it takes its blocks off the palette.
|
|
@@ -18,8 +20,8 @@ export type CanvasPartContribution = {
|
|
|
18
20
|
emoji?: string;
|
|
19
21
|
/**
|
|
20
22
|
* What it arrives with, spelled as the Appspec's text spells it: a
|
|
21
|
-
* connection's, a rule's, a test's or an event's object, a skill's
|
|
22
|
-
* model's id. Every example is one the Appspec's schema takes as it is.
|
|
23
|
+
* connection's, a rule's, a test's or an event's object, a skill's, a
|
|
24
|
+
* model's, a protocol's, a UI's or a character's id. Every example is one the Appspec's schema takes as it is.
|
|
23
25
|
*/
|
|
24
26
|
example: unknown;
|
|
25
27
|
/** Whether what it names is enabled today; a part that is not is offered, and its application's setup notes say so. */
|
|
@@ -157,18 +157,14 @@ export function useWindowPort({ serverUrl, embedToken, session, host, }) {
|
|
|
157
157
|
if (!uid.current) {
|
|
158
158
|
throw new Error(WINDOW_NOT_YET);
|
|
159
159
|
}
|
|
160
|
-
|
|
160
|
+
// What its code answers comes on the ui socket, raised as any
|
|
161
|
+
// window message is (REACTOR_UI RU-W).
|
|
162
|
+
await postWindowMessage({
|
|
161
163
|
serverUrl,
|
|
162
164
|
uid: uid.current,
|
|
163
165
|
...(embedToken ? { token: embedToken } : {}),
|
|
164
166
|
data,
|
|
165
167
|
});
|
|
166
|
-
for (const answer of said) {
|
|
167
|
-
hostRef.current?.onEvent?.({
|
|
168
|
-
type: 'window-message',
|
|
169
|
-
detail: { data: answer },
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
168
|
});
|
|
173
169
|
return () => port(null);
|
|
174
170
|
}, [serverUrl, embedToken, host]);
|
|
@@ -51,7 +51,9 @@ export declare function forgetSessionsInMemory(): void;
|
|
|
51
51
|
/**
|
|
52
52
|
* A session's conversation as the chat draws it, from the AG-UI messages
|
|
53
53
|
* the runtime says (`GET …/sessions/{uid}/messages`): what was said and
|
|
54
|
-
* answered, in words.
|
|
54
|
+
* answered, in words. What its messages showed comes on the conversation's
|
|
55
|
+
* ui socket once the chat opens it (REACTOR_UI RU-W), never here: a
|
|
56
|
+
* `reactor.ui` activity in a thread is refused.
|
|
55
57
|
*/
|
|
56
58
|
export declare function messagesOfThread(messages: unknown): ChatMessage[];
|
|
57
59
|
/** What asking for a kept session found. */
|
|
@@ -79,8 +81,10 @@ export declare function reattachSession({ serverUrl, uid, token, fetcher, }: {
|
|
|
79
81
|
export declare const WINDOW_NOT_YET = "The application takes a window message once its conversation has started, on its own server (the \"server\" attribute).";
|
|
80
82
|
/**
|
|
81
83
|
* Post a window message to a session (LOOP P-25): its code's `@app.window`
|
|
82
|
-
* runs on it.
|
|
83
|
-
*
|
|
84
|
+
* runs on it. What the code tells the page back arrives on the
|
|
85
|
+
* conversation's ui socket (REACTOR_UI RU-W), raised as any window message
|
|
86
|
+
* is; refused — the runtime's sentence — when the session or its code takes
|
|
87
|
+
* none.
|
|
84
88
|
*/
|
|
85
89
|
export declare function postWindowMessage({ serverUrl, uid, token, data, fetcher, }: {
|
|
86
90
|
serverUrl: string;
|
|
@@ -88,6 +92,6 @@ export declare function postWindowMessage({ serverUrl, uid, token, data, fetcher
|
|
|
88
92
|
token?: string;
|
|
89
93
|
data: unknown;
|
|
90
94
|
fetcher?: typeof fetch;
|
|
91
|
-
}): Promise<
|
|
95
|
+
}): Promise<void>;
|
|
92
96
|
/** What the page says, once, when the conversation it kept is gone. */
|
|
93
97
|
export declare const sessionGoneSentence: (name: string) => string;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { windowTurnErrorOf } from '../../chat/base/loopWindow';
|
|
6
|
+
import { AGUI_NAME } from '@datalayer/reactor/ui';
|
|
6
7
|
/** Where a visit's session is kept in the host's storage: `<prefix>:<app>:<visit>`. */
|
|
7
8
|
export const EMBED_SESSION_KEY_PREFIX = 'datalayer-app:session';
|
|
8
9
|
/**
|
|
@@ -93,7 +94,9 @@ export function forgetSessionsInMemory() {
|
|
|
93
94
|
/**
|
|
94
95
|
* A session's conversation as the chat draws it, from the AG-UI messages
|
|
95
96
|
* the runtime says (`GET …/sessions/{uid}/messages`): what was said and
|
|
96
|
-
* answered, in words.
|
|
97
|
+
* answered, in words. What its messages showed comes on the conversation's
|
|
98
|
+
* ui socket once the chat opens it (REACTOR_UI RU-W), never here: a
|
|
99
|
+
* `reactor.ui` activity in a thread is refused.
|
|
97
100
|
*/
|
|
98
101
|
export function messagesOfThread(messages) {
|
|
99
102
|
if (!Array.isArray(messages)) {
|
|
@@ -104,7 +107,10 @@ export function messagesOfThread(messages) {
|
|
|
104
107
|
if (!message || typeof message !== 'object') {
|
|
105
108
|
continue;
|
|
106
109
|
}
|
|
107
|
-
const { id, role, content } = message;
|
|
110
|
+
const { id, role, content, activityType } = message;
|
|
111
|
+
if (role === 'activity' && activityType === AGUI_NAME) {
|
|
112
|
+
throw new Error(`Activity ${String(id)}: what a message showed comes on the ui socket (REACTOR_UI RU-W)`);
|
|
113
|
+
}
|
|
108
114
|
if ((role === 'user' || role === 'assistant') &&
|
|
109
115
|
typeof id === 'string' &&
|
|
110
116
|
typeof content === 'string' &&
|
|
@@ -137,8 +143,10 @@ export async function reattachSession({ serverUrl, uid, token, fetcher = fetch,
|
|
|
137
143
|
export const WINDOW_NOT_YET = 'The application takes a window message once its conversation has started, on its own server (the "server" attribute).';
|
|
138
144
|
/**
|
|
139
145
|
* Post a window message to a session (LOOP P-25): its code's `@app.window`
|
|
140
|
-
* runs on it.
|
|
141
|
-
*
|
|
146
|
+
* runs on it. What the code tells the page back arrives on the
|
|
147
|
+
* conversation's ui socket (REACTOR_UI RU-W), raised as any window message
|
|
148
|
+
* is; refused — the runtime's sentence — when the session or its code takes
|
|
149
|
+
* none.
|
|
142
150
|
*/
|
|
143
151
|
export async function postWindowMessage({ serverUrl, uid, token, data, fetcher = fetch, }) {
|
|
144
152
|
const response = await fetcher(`${serverUrl.replace(/\/+$/, '')}/api/v1/apps/sessions/${encodeURIComponent(uid)}/window`, {
|
|
@@ -160,11 +168,10 @@ export async function postWindowMessage({ serverUrl, uid, token, data, fetcher =
|
|
|
160
168
|
}
|
|
161
169
|
throw new Error(detail || `The window message was refused (${response.status}).`);
|
|
162
170
|
}
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
throw new Error(
|
|
171
|
+
const error = windowTurnErrorOf(await response.text());
|
|
172
|
+
if (error) {
|
|
173
|
+
throw new Error(error);
|
|
166
174
|
}
|
|
167
|
-
return turn.said;
|
|
168
175
|
}
|
|
169
176
|
/** What the page says, once, when the conversation it kept is gone. */
|
|
170
177
|
export const sessionGoneSentence = (name) => `Your earlier conversation with ${name} has ended, so this is a new one.`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application's answer shows, drawn from its display (LOOP P-04,
|
|
3
|
+
* REACTOR_UI RU-2): the A2UI surface in its data, and its buttons pressing
|
|
4
|
+
* only the actions the display offered, each with the token the session
|
|
5
|
+
* minted for it. The answer under its message and an element in a panel or
|
|
6
|
+
* a page of its own are drawn by this one view.
|
|
7
|
+
*
|
|
8
|
+
* @module apps/plugins/a2ui-surface/AnswerShows
|
|
9
|
+
*/
|
|
10
|
+
import type { JSX } from 'react';
|
|
11
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
12
|
+
export type AnswerShowsViewProps = {
|
|
13
|
+
display: Display;
|
|
14
|
+
/**
|
|
15
|
+
* Say something as the reader, with what goes with the run besides it;
|
|
16
|
+
* `false` when the chat cannot take it.
|
|
17
|
+
*/
|
|
18
|
+
send: (message: string, forwardedProps?: Record<string, unknown>) => boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function AnswerShowsView({ display, send, }: AnswerShowsViewProps): JSX.Element;
|
|
21
|
+
export default AnswerShowsView;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Text } from '@primer/react';
|
|
4
|
+
import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
|
|
5
|
+
import { useOwnCatalog } from './ownComponents';
|
|
6
|
+
import { answerPress, answerSurface } from './toolResult';
|
|
7
|
+
export function AnswerShowsView({ display, send, }) {
|
|
8
|
+
const parsed = useMemo(() => answerSurface(display, SURFACE_CATALOG_ID), [display]);
|
|
9
|
+
// With the components the application's developer wrote (LOOP P-17).
|
|
10
|
+
const catalog = useOwnCatalog();
|
|
11
|
+
const [said, setSaid] = useState(null);
|
|
12
|
+
if (!parsed?.messages || parsed.messages.length === 0) {
|
|
13
|
+
return _jsx(Text, { sx: { color: 'fg.muted' }, children: display.text });
|
|
14
|
+
}
|
|
15
|
+
const onAction = (action) => {
|
|
16
|
+
const pressed = answerPress(display, action);
|
|
17
|
+
if ('refused' in pressed) {
|
|
18
|
+
setSaid(pressed.refused);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setSaid(send(pressed.message, pressed.forwardedProps)
|
|
22
|
+
? null
|
|
23
|
+
: 'The chat is not ready to take it yet.');
|
|
24
|
+
};
|
|
25
|
+
return (_jsx(InlineSurface
|
|
26
|
+
// Drawn again whole when the code changes it.
|
|
27
|
+
, { messages: parsed.messages, onAction: onAction, validationError: said, catalog: catalog }, JSON.stringify(display)));
|
|
28
|
+
}
|
|
29
|
+
export default AnswerShowsView;
|
|
@@ -5,7 +5,7 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
5
5
|
import { encodeFormSubmission } from '../../../chat/messages/formSubmission';
|
|
6
6
|
import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
|
|
7
7
|
import { useOwnCatalog } from './ownComponents';
|
|
8
|
-
import {
|
|
8
|
+
import { readA2uiToolResult, validateA2uiSubmission, } from './toolResult';
|
|
9
9
|
export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
10
10
|
const parsed = useMemo(() => context.status === 'complete'
|
|
11
11
|
? readA2uiToolResult(context.result, SURFACE_CATALOG_ID)
|
|
@@ -30,12 +30,9 @@ export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
|
30
30
|
}, [parsed, context.toolCallId]);
|
|
31
31
|
const handleAction = useCallback((action) => {
|
|
32
32
|
if (action.name !== 'submit_a2ui_form') {
|
|
33
|
-
// A
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
if (pressed && !send(pressed.message, pressed.forwardedProps)) {
|
|
37
|
-
setValidationError('The chat is not ready to take it yet.');
|
|
38
|
-
}
|
|
33
|
+
// A tool's surface submits its form, and nothing else: the buttons
|
|
34
|
+
// of what an application's answer shows are its display's
|
|
35
|
+
// (`AnswerShowsView`, REACTOR_UI RU-2).
|
|
39
36
|
return;
|
|
40
37
|
}
|
|
41
38
|
const values = (action.context ?? {});
|
|
@@ -32,5 +32,6 @@ export declare function createA2uiSurfacePlugin(options?: A2uiSurfacePluginOptio
|
|
|
32
32
|
/** The instance the preset mounts: no host callbacks. */
|
|
33
33
|
export declare const A2uiSurfacePlugin: ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
34
34
|
export type { SurfaceRendered, SurfaceSubmitted } from './SurfaceToolResult';
|
|
35
|
-
export {
|
|
35
|
+
export { AnswerShowsView } from './AnswerShows';
|
|
36
|
+
export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, type A2uiFieldRule, type A2uiToolResult, type AnswerPress, } from './toolResult';
|
|
36
37
|
export default A2uiSurfacePlugin;
|
|
@@ -5,6 +5,8 @@ import { Renderers, ToolBindings } from '@datalayer/reactor/ui';
|
|
|
5
5
|
import { defineRenderer, } from '@datalayer/reactor/ui/react';
|
|
6
6
|
import { AGENTSPECS } from '../../../specs/agents';
|
|
7
7
|
import { LoopSlots } from '../../core';
|
|
8
|
+
import { AnswerShowsView } from './AnswerShows';
|
|
9
|
+
import { ANSWER_DISPLAY_TYPE } from './toolResult';
|
|
8
10
|
import { SurfaceToolResult, } from './SurfaceToolResult';
|
|
9
11
|
export const A2UI_SURFACE_PLUGIN_NAME = '@datalayer/loop-plugin-a2ui-surface';
|
|
10
12
|
/** The renderer name a spec binds a tool to. */
|
|
@@ -74,6 +76,11 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
74
76
|
}
|
|
75
77
|
return (_jsx(SurfaceToolResult, { context: context, send: send, onRendered: options.onRendered, onSubmitted: options.onSubmitted }, context.toolCallId));
|
|
76
78
|
}
|
|
79
|
+
// What an application's answer shows (REACTOR_UI RU-2): its buttons press
|
|
80
|
+
// what the display offered, through the workspace's chat.
|
|
81
|
+
function Answer({ display }) {
|
|
82
|
+
return _jsx(AnswerShowsView, { display: display, send: send });
|
|
83
|
+
}
|
|
77
84
|
const plugin = definePlugin({
|
|
78
85
|
name: counter === 1
|
|
79
86
|
? A2UI_SURFACE_PLUGIN_NAME
|
|
@@ -89,6 +96,7 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
89
96
|
// A host's own instance, with its callbacks, over the preset's.
|
|
90
97
|
rank: options.onRendered || options.onSubmitted ? 1 : 0,
|
|
91
98
|
}), { id }),
|
|
99
|
+
contribution(Renderers, defineRenderer({ type: ANSWER_DISPLAY_TYPE, component: Answer }), { id: `${id}:answer` }),
|
|
92
100
|
...[...surfaceToolNames()].map(tool => contribution(ToolBindings, { tool, type: A2UI_SURFACE_TYPE }, { id: `${id}:${tool}` })),
|
|
93
101
|
],
|
|
94
102
|
build() {
|
|
@@ -103,5 +111,6 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
103
111
|
}
|
|
104
112
|
/** The instance the preset mounts: no host callbacks. */
|
|
105
113
|
export const A2uiSurfacePlugin = createA2uiSurfacePlugin();
|
|
106
|
-
export {
|
|
114
|
+
export { AnswerShowsView } from './AnswerShows';
|
|
115
|
+
export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, } from './toolResult';
|
|
107
116
|
export default A2uiSurfacePlugin;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module apps/plugins/a2ui-surface/toolResult
|
|
9
9
|
*/
|
|
10
10
|
import type { A2uiMessage } from '@a2ui/web_core/v0_9';
|
|
11
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
11
12
|
export type A2uiRequiredField = {
|
|
12
13
|
id: string;
|
|
13
14
|
label: string;
|
|
@@ -50,10 +51,13 @@ export declare function isEmptyA2uiValue(value: unknown): boolean;
|
|
|
50
51
|
export declare function validateA2uiField(rule: A2uiFieldRule, value: unknown): string | null;
|
|
51
52
|
/** Every rule's complaint about a submission; empty when it passes. */
|
|
52
53
|
export declare function validateA2uiSubmission(rules: A2uiFieldRule[], values: Record<string, unknown>): string[];
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The display type of what an application's answer shows (LOOP P-04,
|
|
56
|
+
* REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
|
|
57
|
+
*/
|
|
58
|
+
export declare const ANSWER_DISPLAY_TYPE = "application/vnd.datalayer.loop.answer+json";
|
|
59
|
+
/** A button pressed on what an answer shows, as the chat sends it on. */
|
|
60
|
+
export type AnswerPress = {
|
|
57
61
|
/** The person's turn, in words: the action's name. */
|
|
58
62
|
message: string;
|
|
59
63
|
/** What the session API reads besides the message (`forwardedProps.loop`). */
|
|
@@ -61,19 +65,24 @@ export type AnswerAction = {
|
|
|
61
65
|
loop: {
|
|
62
66
|
action: {
|
|
63
67
|
name: string;
|
|
68
|
+
display_id: string;
|
|
69
|
+
token: string;
|
|
64
70
|
payload: Record<string, unknown>;
|
|
65
71
|
};
|
|
66
72
|
};
|
|
67
73
|
};
|
|
68
74
|
};
|
|
75
|
+
/** The A2UI surface an answer's display draws, or `null` for a display that has none. */
|
|
76
|
+
export declare function answerSurface(display: Display, catalogId: string): A2uiToolResult | null;
|
|
69
77
|
/**
|
|
70
|
-
* What a button pressed on
|
|
71
|
-
*
|
|
72
|
-
* with its `@app.action(name)
|
|
73
|
-
*
|
|
78
|
+
* What a button pressed on what an answer shows sends: the action the
|
|
79
|
+
* display offered under that name, with its token and what the button read
|
|
80
|
+
* — the application's code reacts with its `@app.action(name)`. A button
|
|
81
|
+
* whose action the display did not offer sends nothing: the sentence why.
|
|
74
82
|
*/
|
|
75
|
-
export declare function
|
|
83
|
+
export declare function answerPress(display: Display, action: {
|
|
76
84
|
name: string;
|
|
77
|
-
surfaceId: string;
|
|
78
85
|
context?: Record<string, unknown>;
|
|
79
|
-
}):
|
|
86
|
+
}): AnswerPress | {
|
|
87
|
+
refused: string;
|
|
88
|
+
};
|
|
@@ -104,24 +104,40 @@ export function validateA2uiSubmission(rules, values) {
|
|
|
104
104
|
.map(rule => validateA2uiField(rule, values[rule.id]))
|
|
105
105
|
.filter((message) => message !== null);
|
|
106
106
|
}
|
|
107
|
-
/** Where the surface of what an application's answer shows is named (LOOP P-04). */
|
|
108
|
-
export const ANSWER_SURFACE_PREFIX = 'answer-';
|
|
109
107
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* with its `@app.action(name)`, as to a button of its page. `null` for any
|
|
113
|
-
* other surface, whose buttons stay the agent's form submission.
|
|
108
|
+
* The display type of what an application's answer shows (LOOP P-04,
|
|
109
|
+
* REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
|
|
114
110
|
*/
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
export const ANSWER_DISPLAY_TYPE = 'application/vnd.datalayer.loop.answer+json';
|
|
112
|
+
/** The A2UI surface an answer's display draws, or `null` for a display that has none. */
|
|
113
|
+
export function answerSurface(display, catalogId) {
|
|
114
|
+
if (display.type !== ANSWER_DISPLAY_TYPE) {
|
|
118
115
|
return null;
|
|
119
116
|
}
|
|
117
|
+
return readA2uiToolResult(display.data?.surface, catalogId);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* What a button pressed on what an answer shows sends: the action the
|
|
121
|
+
* display offered under that name, with its token and what the button read
|
|
122
|
+
* — the application's code reacts with its `@app.action(name)`. A button
|
|
123
|
+
* whose action the display did not offer sends nothing: the sentence why.
|
|
124
|
+
*/
|
|
125
|
+
export function answerPress(display, action) {
|
|
126
|
+
const name = action.name.trim();
|
|
127
|
+
const offer = (display.actions ?? []).find(one => one.name === name);
|
|
128
|
+
if (!offer) {
|
|
129
|
+
return { refused: `“${name || 'This button'}” was not offered here.` };
|
|
130
|
+
}
|
|
120
131
|
return {
|
|
121
|
-
message:
|
|
132
|
+
message: name,
|
|
122
133
|
forwardedProps: {
|
|
123
134
|
loop: {
|
|
124
|
-
action: {
|
|
135
|
+
action: {
|
|
136
|
+
name,
|
|
137
|
+
display_id: display.id,
|
|
138
|
+
token: offer.token,
|
|
139
|
+
payload: { ...action.context },
|
|
140
|
+
},
|
|
125
141
|
},
|
|
126
142
|
},
|
|
127
143
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `app-components` — the components an application's developer wrote (LOOP
|
|
3
3
|
* P-17), registered as that application's A2UI contributions: each a block
|
|
4
|
-
* of its Canvas's palette (`loop.canvas.block`, as a UI
|
|
4
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI's components
|
|
5
5
|
* are) and a renderer of the catalog its page, its elements and its answers
|
|
6
6
|
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
7
7
|
* workspace's preset only: the catalog grows for it, it does not open.
|