@datalayer/agent-runtimes 1.3.84 → 1.3.85
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/lib/chat/ChatFloating.d.ts +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +14 -11
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +3 -0
- package/lib/components/teams/index.js +3 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/loop/apps/AppComposer.d.ts +2 -1
- package/lib/loop/apps/AppComposer.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +2 -1
- package/lib/loop/apps/AppRenderer.js +34 -25
- package/lib/loop/apps/appspec.d.ts +11 -1
- package/lib/loop/apps/appspec.js +115 -1
- package/lib/loop/apps/checks.d.ts +8 -0
- package/lib/loop/apps/checks.js +106 -4
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +1 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +11 -3
- package/lib/loop/embed/AppEmbed.js +42 -16
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +61 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/hostBridge.d.ts +28 -2
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.js +4 -1
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +120 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +6 -4
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1384 -231
- package/lib/specs/appspecSchema.js +191 -1
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/types/agentspecs.d.ts +70 -0
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +4 -2
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
package/lib/loop/apps/checks.js
CHANGED
|
@@ -39,12 +39,13 @@ import { getTeamSpec } from '../../specs/teams';
|
|
|
39
39
|
import { getBackendToolSpec } from '../../specs/backendTools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
41
|
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
42
|
-
import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, parseAppspec, } from './appspec';
|
|
42
|
+
import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, PAGE_OUTPUT_SHOWS, parseAppspec, } from './appspec';
|
|
43
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
44
44
|
import { COMMAND_INPUT, COMMAND_NAME, MODE_ID } from './composer';
|
|
45
|
-
import { HOST_NAME, hostToolsOf } from './hostTools';
|
|
45
|
+
import { HOST_NAME, HOST_USERS, hostToolsOf } from './hostTools';
|
|
46
46
|
import { MAX_UPLOAD_MB, UPLOAD_KIND } from './uploads';
|
|
47
47
|
import { formUiProblems } from './settingsInputs';
|
|
48
|
+
import { customComponentOf, customComponentsProblems, customNodeRefused, customPropsRefused, } from './customComponents';
|
|
48
49
|
import { translationProblems } from './language';
|
|
49
50
|
export const NOT_READY = 'Not ready';
|
|
50
51
|
export const NEEDS_ATTENTION = 'Needs attention';
|
|
@@ -129,6 +130,82 @@ export function ownCodeProblems(app) {
|
|
|
129
130
|
if (new Set(decided).size !== decided.length) {
|
|
130
131
|
problems.push('Two tests are decided by the same function of its code.');
|
|
131
132
|
}
|
|
133
|
+
problems.push(...pageProblems(app));
|
|
134
|
+
// The components its developer wrote, reviewed as the catalog's (P-17).
|
|
135
|
+
problems.push(...customComponentsProblems(app));
|
|
136
|
+
return problems;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* What agentspecs refuses of a widget's page written in its code (LOOP
|
|
140
|
+
* P-05): a page that is not a widget's, inputs that are not a form, outputs
|
|
141
|
+
* of one name twice, drawn with another component, saying in their `props`
|
|
142
|
+
* what their value fills or lacking what their component needs, and an input
|
|
143
|
+
* named as a setting — both are on the page at `/inputs/<name>`.
|
|
144
|
+
*/
|
|
145
|
+
export function pageProblems(app) {
|
|
146
|
+
const page = app.interface.page;
|
|
147
|
+
if (!page) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const problems = [];
|
|
151
|
+
if (app.kind !== 'widget') {
|
|
152
|
+
problems.push(`A page of inputs and outputs is a widget's: a ${app.kind} application has none. Remove \`interface.page\`, or make it a widget.`);
|
|
153
|
+
}
|
|
154
|
+
if (!CODE_NAME.test(page.function)) {
|
|
155
|
+
problems.push(`Cannot use “${page.function}” as the function of its page: letters, digits and \`_\`, as in Python.`);
|
|
156
|
+
}
|
|
157
|
+
problems.push(...formProblems({
|
|
158
|
+
id: 'page inputs',
|
|
159
|
+
schema: page.inputs,
|
|
160
|
+
ui: page.inputsUi,
|
|
161
|
+
}));
|
|
162
|
+
if (page.outputs.length === 0) {
|
|
163
|
+
problems.push('Its page shows one output at least.');
|
|
164
|
+
}
|
|
165
|
+
const names = page.outputs.map(output => output.name);
|
|
166
|
+
if (new Set(names).size !== names.length) {
|
|
167
|
+
problems.push('Two outputs of its page have the same name.');
|
|
168
|
+
}
|
|
169
|
+
for (const output of page.outputs) {
|
|
170
|
+
if (!CODE_NAME.test(output.name)) {
|
|
171
|
+
problems.push(`Cannot use “${output.name}” as an output's name: letters, digits and \`_\`, as in Python.`);
|
|
172
|
+
}
|
|
173
|
+
// A component its developer wrote (P-17): its value what it shows first.
|
|
174
|
+
const custom = output.component in PAGE_OUTPUT_SHOWS
|
|
175
|
+
? undefined
|
|
176
|
+
: customComponentOf(app, output.component);
|
|
177
|
+
if (custom) {
|
|
178
|
+
if (custom.shows.length === 0) {
|
|
179
|
+
problems.push(`The output “${output.name}” is drawn with ${output.component}, which shows nothing: its value is what it shows first, under \`shows\`.`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const refused = customPropsRefused(custom, output.props);
|
|
183
|
+
if (refused.length > 0) {
|
|
184
|
+
problems.push(`The output “${output.name}” is a ${output.component} its schema refuses: ${refused.join('; ')}.`);
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const shows = PAGE_OUTPUT_SHOWS[output.component];
|
|
189
|
+
if (!shows) {
|
|
190
|
+
problems.push(`The output “${output.name}” is drawn with ${output.component}: an output is one of ${Object.keys(PAGE_OUTPUT_SHOWS).join(', ')} or a component of its own (\`custom_components\`).`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const said = [shows, 'id', 'component'].filter(key => key in output.props);
|
|
194
|
+
if (said.length > 0) {
|
|
195
|
+
problems.push(`The output “${output.name}” says ${said.join(', ')} in its \`props\`: its id is its name, and what it shows is its value.`);
|
|
196
|
+
}
|
|
197
|
+
const schema = getComponent(output.component)?.properties;
|
|
198
|
+
const missing = (schema?.required ?? []).filter(key => key !== shows && !(key in output.props));
|
|
199
|
+
if (missing.length > 0) {
|
|
200
|
+
problems.push(`The output “${output.name}” is a ${output.component} without ${missing.join(', ')}: say it in its \`props\`.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const settings = Object.keys(app.interface.settings?.properties ?? {});
|
|
204
|
+
const inputs = Object.keys(page.inputs?.properties ?? {});
|
|
205
|
+
const shared = settings.filter(name => inputs.includes(name));
|
|
206
|
+
if (shared.length > 0) {
|
|
207
|
+
problems.push(`${shared.map(name => `“${name}”`).join(', ')} is both a setting and an input of its page: name one otherwise.`);
|
|
208
|
+
}
|
|
132
209
|
return problems;
|
|
133
210
|
}
|
|
134
211
|
/** What the spec says of itself that the tolerant reader lets through. */
|
|
@@ -228,6 +305,10 @@ function shapeProblems(app) {
|
|
|
228
305
|
if (new Set(names).size !== names.length) {
|
|
229
306
|
problems.push('The host’s functions are named once each.');
|
|
230
307
|
}
|
|
308
|
+
// Who its user is (D-21): what the page says, or what its server signed.
|
|
309
|
+
if (host.user !== undefined && !HOST_USERS.includes(host.user)) {
|
|
310
|
+
problems.push(`“${host.user}” is not how the host’s user is taken: claimed or signed.`);
|
|
311
|
+
}
|
|
231
312
|
const named = new Set(app.rules.flatMap(rule => rule.appliesTo));
|
|
232
313
|
for (const tool of hostToolsOf(host)) {
|
|
233
314
|
if (!named.has(tool)) {
|
|
@@ -380,13 +461,21 @@ function referenceProblems(app, organizationFrames) {
|
|
|
380
461
|
}
|
|
381
462
|
}
|
|
382
463
|
// The components it may use, and those its surface uses, are the catalog's (C-13).
|
|
464
|
+
// Those its developer wrote are of the catalog for it alone (P-17).
|
|
383
465
|
for (const name of app.interface.components ?? []) {
|
|
384
|
-
if (!componentNamed(name)) {
|
|
466
|
+
if (!componentNamed(name) && !customComponentOf(app, name)) {
|
|
385
467
|
problems.push(`There is no component named “${name}” in the catalog.`);
|
|
386
468
|
}
|
|
387
469
|
}
|
|
388
470
|
for (const node of app.interface.surface?.components ?? []) {
|
|
389
|
-
|
|
471
|
+
const custom = customComponentOf(app, String(node.component));
|
|
472
|
+
if (custom) {
|
|
473
|
+
const refused = customNodeRefused(custom, node);
|
|
474
|
+
if (refused.length > 0) {
|
|
475
|
+
problems.push(`The surface's “${String(node.id)}” is a ${custom.name} its schema refuses: ${refused.join('; ')}.`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else if (!componentNamed(String(node.component))) {
|
|
390
479
|
problems.push(`The surface's “${String(node.id)}” is a “${String(node.component)}”, which the catalog does not have.`);
|
|
391
480
|
}
|
|
392
481
|
else if (node.component === 'Form') {
|
|
@@ -912,6 +1001,19 @@ export function documentShapeProblems(document) {
|
|
|
912
1001
|
required(item, 'id', where);
|
|
913
1002
|
required(item, 'component', where);
|
|
914
1003
|
});
|
|
1004
|
+
}); // A widget's page written in its code (LOOP P-05), as agentspecs reads it.
|
|
1005
|
+
mapping(ui.page, 'interface.page', page => {
|
|
1006
|
+
unknownKeys(page, ['function', 'inputs', 'inputs_ui', 'outputs', 'live'], 'interface.page', 'a page');
|
|
1007
|
+
required(page, 'function', 'interface.page');
|
|
1008
|
+
required(page, 'inputs', 'interface.page');
|
|
1009
|
+
required(page, 'outputs', 'interface.page');
|
|
1010
|
+
if (page.live !== undefined && typeof page.live !== 'boolean') {
|
|
1011
|
+
at('interface.page.live', 'is true or false');
|
|
1012
|
+
}
|
|
1013
|
+
records(page.outputs, 'interface.page.outputs', (output, where) => {
|
|
1014
|
+
required(output, 'name', where);
|
|
1015
|
+
unknownKeys(output, ['name', 'title', 'component', 'props'], where, 'an output');
|
|
1016
|
+
});
|
|
915
1017
|
});
|
|
916
1018
|
});
|
|
917
1019
|
mapping(d.tests, 'tests', tests => {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppCustomComponentSpec, AppSpec, ComponentSpec } from '../../types/agentspecs';
|
|
2
|
+
/** Its name: a word starting with a capital letter, as the catalog's are named. */
|
|
3
|
+
export declare const CUSTOM_COMPONENT_NAME: RegExp;
|
|
4
|
+
/** What one of its properties may be typed: what the renderer tells apart. */
|
|
5
|
+
export declare const CUSTOM_PROPERTY_TYPES: readonly ["string", "integer", "number", "boolean", "array", "object"];
|
|
6
|
+
/** What every component of a surface already has, and a custom one cannot declare. */
|
|
7
|
+
export declare const CUSTOM_RESERVED_NAMES: readonly ["id", "component", "action", "weight", "visible_when", "children", "child"];
|
|
8
|
+
/**
|
|
9
|
+
* Where its module is loaded from: an address served over HTTPS, or this
|
|
10
|
+
* machine while it is written. A file of the application's folder waits for
|
|
11
|
+
* its packaging (P-29).
|
|
12
|
+
*/
|
|
13
|
+
export declare const CUSTOM_SOURCE: RegExp;
|
|
14
|
+
/** A Subresource Integrity hash: the module as it was reviewed. */
|
|
15
|
+
export declare const INTEGRITY: RegExp;
|
|
16
|
+
/**
|
|
17
|
+
* What its schema refuses of the properties given, in the words agentspecs
|
|
18
|
+
* says them: one it does not have, a value of another type, one it needs.
|
|
19
|
+
* `bound` are given elsewhere — bound to what the application publishes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function customPropsRefused(component: AppCustomComponentSpec, props: Record<string, unknown>, bound?: readonly string[]): string[];
|
|
22
|
+
/** What stops one component its developer wrote from being reviewed, in sentences. */
|
|
23
|
+
export declare function customComponentProblems(component: AppCustomComponentSpec): string[];
|
|
24
|
+
/** What stops the components an application's developer wrote, in sentences. */
|
|
25
|
+
export declare function customComponentsProblems(app: AppSpec): string[];
|
|
26
|
+
/** A component its developer wrote, by name, or undefined. */
|
|
27
|
+
export declare function customComponentOf(app: Pick<AppSpec, 'interface'>, name: string): AppCustomComponentSpec | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* It as the catalog lists a component (C-13): what the palette, the
|
|
30
|
+
* components page and the checks read — under the application's version, its
|
|
31
|
+
* category `custom`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function customComponentEntry(component: AppCustomComponentSpec, version: string): ComponentSpec;
|
|
34
|
+
/**
|
|
35
|
+
* The components an application may place: the catalog's own, given, and
|
|
36
|
+
* those its developer wrote, for it alone. A name of its own that the
|
|
37
|
+
* catalog has is not listed twice: the checks refuse it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function appComponents(app: Pick<AppSpec, 'interface' | 'version'>, catalog: readonly ComponentSpec[]): ComponentSpec[];
|
|
40
|
+
/** What its schema refuses of a node of a surface drawn by it, in sentences; empty when nothing. */
|
|
41
|
+
export declare function customNodeRefused(component: AppCustomComponentSpec, node: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Components a developer writes (LOOP P-17): an A2UI component of one
|
|
7
|
+
* application only, declared in its Appspec (`interface.custom_components`)
|
|
8
|
+
* with its name, its properties as a JSON Schema, what it shows and sends,
|
|
9
|
+
* and the address of the built ES module that draws it.
|
|
10
|
+
*
|
|
11
|
+
* Reviewed like any component of the catalog — what agentspecs refuses is
|
|
12
|
+
* refused here in the same sentences — and listed beside the catalog's for
|
|
13
|
+
* that application alone (`appComponents`): the catalog grows for it, it
|
|
14
|
+
* does not open. How it is drawn, in a sandboxed frame of no origin, is
|
|
15
|
+
* `components/a2ui/custom`.
|
|
16
|
+
*
|
|
17
|
+
* @module loop/apps/customComponents
|
|
18
|
+
*/
|
|
19
|
+
import { getComponent } from '../../specs/uiPlugins';
|
|
20
|
+
/** Its name: a word starting with a capital letter, as the catalog's are named. */
|
|
21
|
+
export const CUSTOM_COMPONENT_NAME = /^[A-Z][A-Za-z0-9]{0,63}$/;
|
|
22
|
+
/** What one of its properties may be typed: what the renderer tells apart. */
|
|
23
|
+
export const CUSTOM_PROPERTY_TYPES = [
|
|
24
|
+
'string',
|
|
25
|
+
'integer',
|
|
26
|
+
'number',
|
|
27
|
+
'boolean',
|
|
28
|
+
'array',
|
|
29
|
+
'object',
|
|
30
|
+
];
|
|
31
|
+
/** What every component of a surface already has, and a custom one cannot declare. */
|
|
32
|
+
export const CUSTOM_RESERVED_NAMES = [
|
|
33
|
+
'id',
|
|
34
|
+
'component',
|
|
35
|
+
'action',
|
|
36
|
+
'weight',
|
|
37
|
+
'visible_when',
|
|
38
|
+
'children',
|
|
39
|
+
'child',
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Where its module is loaded from: an address served over HTTPS, or this
|
|
43
|
+
* machine while it is written. A file of the application's folder waits for
|
|
44
|
+
* its packaging (P-29).
|
|
45
|
+
*/
|
|
46
|
+
export const CUSTOM_SOURCE = /^(?:https:\/\/[A-Za-z0-9.-]+(?::\d+)?|http:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?)\/\S*$/;
|
|
47
|
+
/** A Subresource Integrity hash: the module as it was reviewed. */
|
|
48
|
+
export const INTEGRITY = /^sha(?:256|384|512)-[A-Za-z0-9+/]+={0,2}$/;
|
|
49
|
+
const PROPERTY_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
50
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
|
+
const isBound = (value) => isRecord(value) && 'path' in value;
|
|
52
|
+
const quoted = (name) => `'${name}'`;
|
|
53
|
+
/** Why a value is not one a property takes, or null. */
|
|
54
|
+
function valueRefused(name, field, value) {
|
|
55
|
+
if (Array.isArray(field.enum)) {
|
|
56
|
+
return field.enum.includes(value)
|
|
57
|
+
? null
|
|
58
|
+
: `${quoted(name)} is one of ${field.enum.map(each => quoted(String(each))).join(', ')}`;
|
|
59
|
+
}
|
|
60
|
+
const kind = String(field.type);
|
|
61
|
+
const takes = {
|
|
62
|
+
string: v => typeof v === 'string',
|
|
63
|
+
integer: v => typeof v === 'number' && Number.isInteger(v),
|
|
64
|
+
number: v => typeof v === 'number',
|
|
65
|
+
boolean: v => typeof v === 'boolean',
|
|
66
|
+
array: v => Array.isArray(v),
|
|
67
|
+
object: isRecord,
|
|
68
|
+
};
|
|
69
|
+
if (typeof value === 'boolean' && kind !== 'boolean') {
|
|
70
|
+
return `${quoted(name)} is ${kind}, not true or false`;
|
|
71
|
+
}
|
|
72
|
+
return takes[kind]?.(value) ? null : `${quoted(name)} is ${kind}`;
|
|
73
|
+
}
|
|
74
|
+
/** Its properties, as its schema declares them. */
|
|
75
|
+
const fieldsOf = (component) => (isRecord(component.props.properties)
|
|
76
|
+
? component.props.properties
|
|
77
|
+
: {});
|
|
78
|
+
/**
|
|
79
|
+
* What its schema refuses of the properties given, in the words agentspecs
|
|
80
|
+
* says them: one it does not have, a value of another type, one it needs.
|
|
81
|
+
* `bound` are given elsewhere — bound to what the application publishes.
|
|
82
|
+
*/
|
|
83
|
+
export function customPropsRefused(component, props, bound = []) {
|
|
84
|
+
const fields = fieldsOf(component);
|
|
85
|
+
const problems = Object.keys(props)
|
|
86
|
+
.filter(name => !(name in fields))
|
|
87
|
+
.map(name => `it has no property ${quoted(name)}`);
|
|
88
|
+
for (const [name, value] of Object.entries(props)) {
|
|
89
|
+
if (name in fields && !isBound(value)) {
|
|
90
|
+
const refused = valueRefused(name, fields[name], value);
|
|
91
|
+
if (refused) {
|
|
92
|
+
problems.push(refused);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const required = Array.isArray(component.props.required)
|
|
97
|
+
? component.props.required
|
|
98
|
+
: [];
|
|
99
|
+
for (const name of required) {
|
|
100
|
+
if (!(name in props) && !bound.includes(name)) {
|
|
101
|
+
problems.push(`it needs ${quoted(name)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return problems;
|
|
105
|
+
}
|
|
106
|
+
/** What stops one component its developer wrote from being reviewed, in sentences. */
|
|
107
|
+
export function customComponentProblems(component) {
|
|
108
|
+
const said = `The component ${component.name || '(unnamed)'}`;
|
|
109
|
+
const problems = [];
|
|
110
|
+
if (!CUSTOM_COMPONENT_NAME.test(component.name)) {
|
|
111
|
+
problems.push(`Cannot use “${component.name}” as a component's name: a word starting with a capital letter, as Gauge.`);
|
|
112
|
+
}
|
|
113
|
+
else if (getComponent(component.name)) {
|
|
114
|
+
problems.push(`${said} is a component of the catalog: name yours otherwise.`);
|
|
115
|
+
}
|
|
116
|
+
if (!component.description.trim()) {
|
|
117
|
+
problems.push(`${said} says what it is for, under \`description\`.`);
|
|
118
|
+
}
|
|
119
|
+
if (!/^[a-z][a-z0-9+.-]*:/.test(component.source)) {
|
|
120
|
+
problems.push(`${said}'s module “${component.source}” is a file of the application's folder: it is drawn once the application is packaged with it (LOOP P-29); give the address of a built ES module.`);
|
|
121
|
+
}
|
|
122
|
+
else if (!CUSTOM_SOURCE.test(component.source)) {
|
|
123
|
+
problems.push(`${said}'s module “${component.source}” is loaded over \`https://\`, or from \`http://localhost\` while it is written.`);
|
|
124
|
+
}
|
|
125
|
+
if (component.integrity && !INTEGRITY.test(component.integrity)) {
|
|
126
|
+
problems.push(`${said}'s integrity “${component.integrity}” is no Subresource Integrity hash: \`sha384-\` and the hash in base64.`);
|
|
127
|
+
}
|
|
128
|
+
if (component.height < 40 || component.height > 2000) {
|
|
129
|
+
problems.push(`${said} is between 40 and 2000 pixels high.`);
|
|
130
|
+
}
|
|
131
|
+
const schema = component.props;
|
|
132
|
+
if (schema.type !== 'object' || !isRecord(schema.properties)) {
|
|
133
|
+
problems.push(`${said}'s props are the JSON Schema of an object, with its \`properties\`.`);
|
|
134
|
+
return problems;
|
|
135
|
+
}
|
|
136
|
+
const fields = fieldsOf(component);
|
|
137
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
138
|
+
if (!PROPERTY_NAME.test(name) ||
|
|
139
|
+
CUSTOM_RESERVED_NAMES.includes(name)) {
|
|
140
|
+
problems.push(`${said} cannot have a property named ${quoted(name)}.`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!isRecord(field)) {
|
|
144
|
+
problems.push(`${said}'s property ${quoted(name)} is not a schema.`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if ('enum' in field) {
|
|
148
|
+
const values = field.enum;
|
|
149
|
+
if (!Array.isArray(values) ||
|
|
150
|
+
values.length === 0 ||
|
|
151
|
+
!values.every(value => typeof value === 'string')) {
|
|
152
|
+
problems.push(`${said}'s property ${quoted(name)} is an \`enum\` of words, one at least.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (!CUSTOM_PROPERTY_TYPES.includes(field.type)) {
|
|
156
|
+
problems.push(`${said}'s property ${quoted(name)} is typed ${quoted(String(field.type))}: one of ${CUSTOM_PROPERTY_TYPES.join(', ')}, or an \`enum\`.`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if ('default' in field) {
|
|
160
|
+
const refused = valueRefused(name, field, field.default);
|
|
161
|
+
if (refused) {
|
|
162
|
+
problems.push(`${said}'s default for ${refused}.`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const required = Array.isArray(schema.required)
|
|
167
|
+
? schema.required
|
|
168
|
+
: [];
|
|
169
|
+
const missing = required.filter(name => !(name in fields));
|
|
170
|
+
if (missing.length > 0) {
|
|
171
|
+
problems.push(`${said} requires ${missing.map(quoted).join(', ')}, which its props do not have.`);
|
|
172
|
+
}
|
|
173
|
+
for (const name of [...component.shows, ...component.sends]) {
|
|
174
|
+
if (!PROPERTY_NAME.test(name) ||
|
|
175
|
+
CUSTOM_RESERVED_NAMES.includes(name)) {
|
|
176
|
+
problems.push(`${said} cannot bind through ${quoted(name)}.`);
|
|
177
|
+
}
|
|
178
|
+
else if (name in fields) {
|
|
179
|
+
problems.push(`${said} both has the property ${quoted(name)} and binds through it.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const [bindings, what] of [
|
|
183
|
+
[component.shows, 'shows'],
|
|
184
|
+
[component.sends, 'sends'],
|
|
185
|
+
]) {
|
|
186
|
+
if (new Set(bindings).size !== bindings.length) {
|
|
187
|
+
problems.push(`${said} ${what} the same binding twice.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (component.example) {
|
|
191
|
+
const bindings = new Set([...component.shows, ...component.sends]);
|
|
192
|
+
const given = Object.fromEntries(Object.entries(component.example).filter(([key]) => !bindings.has(key)));
|
|
193
|
+
const refused = customPropsRefused(component, given);
|
|
194
|
+
if (refused.length > 0) {
|
|
195
|
+
problems.push(`${said}'s example is refused: ${refused.join('; ')}.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return problems;
|
|
199
|
+
}
|
|
200
|
+
/** What stops the components an application's developer wrote, in sentences. */
|
|
201
|
+
export function customComponentsProblems(app) {
|
|
202
|
+
const own = app.interface.customComponents ?? [];
|
|
203
|
+
const problems = own.flatMap(customComponentProblems);
|
|
204
|
+
const names = own.map(component => component.name);
|
|
205
|
+
if (new Set(names).size !== names.length) {
|
|
206
|
+
problems.push('Two of its own components have the same name.');
|
|
207
|
+
}
|
|
208
|
+
return problems;
|
|
209
|
+
}
|
|
210
|
+
/** A component its developer wrote, by name, or undefined. */
|
|
211
|
+
export function customComponentOf(app, name) {
|
|
212
|
+
return (app.interface.customComponents ?? []).find(component => component.name === name);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* It as the catalog lists a component (C-13): what the palette, the
|
|
216
|
+
* components page and the checks read — under the application's version, its
|
|
217
|
+
* category `custom`.
|
|
218
|
+
*/
|
|
219
|
+
export function customComponentEntry(component, version) {
|
|
220
|
+
return {
|
|
221
|
+
id: component.name,
|
|
222
|
+
name: component.name,
|
|
223
|
+
description: component.description,
|
|
224
|
+
category: 'custom',
|
|
225
|
+
emoji: '🧩',
|
|
226
|
+
version,
|
|
227
|
+
standard: false,
|
|
228
|
+
properties: component.props,
|
|
229
|
+
bindings: { shows: [...component.shows], sends: [...component.sends] },
|
|
230
|
+
events: component.sends.length > 0 ? ['send'] : [],
|
|
231
|
+
...(component.example ? { example: component.example } : {}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The components an application may place: the catalog's own, given, and
|
|
236
|
+
* those its developer wrote, for it alone. A name of its own that the
|
|
237
|
+
* catalog has is not listed twice: the checks refuse it.
|
|
238
|
+
*/
|
|
239
|
+
export function appComponents(app, catalog) {
|
|
240
|
+
const names = new Set(catalog.map(component => component.id));
|
|
241
|
+
return [
|
|
242
|
+
...catalog,
|
|
243
|
+
...(app.interface.customComponents ?? [])
|
|
244
|
+
.filter(component => !names.has(component.name))
|
|
245
|
+
.map(component => customComponentEntry(component, app.version)),
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
/** What its schema refuses of a node of a surface drawn by it, in sentences; empty when nothing. */
|
|
249
|
+
export function customNodeRefused(component, node) {
|
|
250
|
+
const skip = new Set([
|
|
251
|
+
...CUSTOM_RESERVED_NAMES,
|
|
252
|
+
...component.shows,
|
|
253
|
+
...component.sends,
|
|
254
|
+
]);
|
|
255
|
+
const given = Object.fromEntries(Object.entries(node).filter(([key]) => !skip.has(key)));
|
|
256
|
+
const bound = Object.entries(given)
|
|
257
|
+
.filter(([, value]) => isBound(value))
|
|
258
|
+
.map(([key]) => key);
|
|
259
|
+
return customPropsRefused(component, given, bound);
|
|
260
|
+
}
|
|
@@ -6,12 +6,19 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module loop/apps/hostTools
|
|
8
8
|
*/
|
|
9
|
-
import type { AppHostBridgeSpec } from '../../types/agentspecs';
|
|
9
|
+
import type { AppHostBridgeSpec, AppHostUser } from '../../types/agentspecs';
|
|
10
10
|
/** The tool its agent reads what the page passes it with. */
|
|
11
11
|
export declare const HOST_CONTEXT_TOOL = "host_context";
|
|
12
12
|
/** The tool its agent calls a function of the page with. */
|
|
13
13
|
export declare const hostTool: (name: string) => string;
|
|
14
14
|
/** How a value or a function of the host is named. */
|
|
15
15
|
export declare const HOST_NAME: RegExp;
|
|
16
|
+
/** What the page's word on its visitor may be worth (LOOP D-21). */
|
|
17
|
+
export declare const HOST_USERS: readonly AppHostUser[];
|
|
18
|
+
/**
|
|
19
|
+
* Whether a session is opened only for a user the host's server signed
|
|
20
|
+
* (LOOP D-21): `deployment.embedded.host.user: signed`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function signedUser(bridge: AppHostBridgeSpec | undefined): boolean;
|
|
16
23
|
/** The tools its agent is given for the host. */
|
|
17
24
|
export declare function hostToolsOf(bridge: AppHostBridgeSpec | undefined): string[];
|
|
@@ -8,6 +8,15 @@ export const HOST_CONTEXT_TOOL = 'host_context';
|
|
|
8
8
|
export const hostTool = (name) => `host_${name}`;
|
|
9
9
|
/** How a value or a function of the host is named. */
|
|
10
10
|
export const HOST_NAME = /^[a-z][a-z0-9_]{0,62}$/;
|
|
11
|
+
/** What the page's word on its visitor may be worth (LOOP D-21). */
|
|
12
|
+
export const HOST_USERS = ['claimed', 'signed'];
|
|
13
|
+
/**
|
|
14
|
+
* Whether a session is opened only for a user the host's server signed
|
|
15
|
+
* (LOOP D-21): `deployment.embedded.host.user: signed`.
|
|
16
|
+
*/
|
|
17
|
+
export function signedUser(bridge) {
|
|
18
|
+
return bridge?.user === 'signed';
|
|
19
|
+
}
|
|
11
20
|
/** The tools its agent is given for the host. */
|
|
12
21
|
export function hostToolsOf(bridge) {
|
|
13
22
|
if (!bridge) {
|
package/lib/loop/apps/index.d.ts
CHANGED
package/lib/loop/apps/index.js
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactComponentImplementation } from '@a2ui/react/v0_9';
|
|
2
|
+
export type A2uiComponentContribution = {
|
|
3
|
+
/** The name a surface gives it (e.g. 'Gauge'). */
|
|
4
|
+
id: string;
|
|
5
|
+
/** The application whose developer wrote it, by id. */
|
|
6
|
+
app: string;
|
|
7
|
+
/** Its renderer, as A2UI's catalog registers one. */
|
|
8
|
+
implementation: ReactComponentImplementation;
|
|
9
|
+
};
|
|
10
|
+
export declare const LoopA2uiComponent: import("@datalayer/reactor").ContributionPoint<A2uiComponentContribution>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A component an application adds to the A2UI catalog it is drawn with (LOOP
|
|
7
|
+
* P-17), as a contribution point, re-exported by `loop/core`: the components
|
|
8
|
+
* its developer wrote, contributed by the application's own plugin, so that
|
|
9
|
+
* its page, the elements its code shows and its answers draw them — in that
|
|
10
|
+
* application's workspace, and in no other.
|
|
11
|
+
*
|
|
12
|
+
* @module loop/core/a2uiComponents
|
|
13
|
+
*/
|
|
14
|
+
import { defineContributionPoint } from '@datalayer/reactor';
|
|
15
|
+
export const LoopA2uiComponent = defineContributionPoint('loop.a2ui.component');
|
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -579,6 +579,7 @@ export type AssistantMenuContribution = {
|
|
|
579
579
|
};
|
|
580
580
|
export declare const LoopAssistantMenu: import("@datalayer/reactor").ContributionPoint<AssistantMenuContribution>;
|
|
581
581
|
export { LoopCanvasBlock, type CanvasBlockContribution } from './canvasBlocks';
|
|
582
|
+
export { LoopA2uiComponent, type A2uiComponentContribution, } from './a2uiComponents';
|
|
582
583
|
/**
|
|
583
584
|
* The per-example chat extras a host feeds the loop's conversation live.
|
|
584
585
|
*
|
package/lib/loop/core/index.js
CHANGED
|
@@ -146,6 +146,9 @@ export const LoopAssistantMenu = defineContributionPoint('loop.assistant.menu');
|
|
|
146
146
|
// A block the Canvas may place (LOOP C-12), in a module of its own so that
|
|
147
147
|
// the palette is read without the rest of the contracts.
|
|
148
148
|
export { LoopCanvasBlock } from './canvasBlocks';
|
|
149
|
+
// A component an application adds to the catalog it is drawn with (LOOP
|
|
150
|
+
// P-17): the components its developer wrote, for its workspace alone.
|
|
151
|
+
export { LoopA2uiComponent, } from './a2uiComponents';
|
|
149
152
|
export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
|
|
150
153
|
export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
|
|
151
154
|
export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
|
-
import type
|
|
2
|
+
import { type PluginRef } from '@datalayer/reactor';
|
|
3
3
|
import type { AppAccent, AppEmbedMode, AppSpec, AppThemeVariant } from '../../types/agentspecs';
|
|
4
4
|
import type { ResumedThread, ThemeOverrides } from '../../types/chat';
|
|
5
5
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
@@ -24,6 +24,12 @@ export type AppEmbedProps = {
|
|
|
24
24
|
colorMode?: EmbedColorMode;
|
|
25
25
|
/** The host's face, a CSS font family; the theme's by default. */
|
|
26
26
|
font?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The language the visitor reads it in, as BCP 47 tags it (`fr`, `pt-BR`):
|
|
29
|
+
* its translation into it and the chat's own words (LOOP P-26). The
|
|
30
|
+
* visitor's browser's by default.
|
|
31
|
+
*/
|
|
32
|
+
language?: string;
|
|
27
33
|
/**
|
|
28
34
|
* An agent-runtimes server the application's agent runs on. Without one,
|
|
29
35
|
* it runs on a Datalayer runtime, launched with the visitor's Datalayer
|
|
@@ -150,6 +156,8 @@ export type AppFloatingProps = {
|
|
|
150
156
|
host?: AppEmbedHost;
|
|
151
157
|
/** The visit's session, picked up again after a reload (D-13). */
|
|
152
158
|
session?: EmbedSession;
|
|
159
|
+
/** The language the visitor reads it in (P-26); the browser's by default. */
|
|
160
|
+
language?: string;
|
|
153
161
|
};
|
|
154
162
|
/**
|
|
155
163
|
* The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
|
|
@@ -160,7 +168,7 @@ export type AppFloatingProps = {
|
|
|
160
168
|
* The workspace tells the chrome what the application is doing and what it
|
|
161
169
|
* last said (`onPresence`, `onSaying`).
|
|
162
170
|
*/
|
|
163
|
-
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }: AppFloatingProps): JSX.Element;
|
|
171
|
+
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, language, }: AppFloatingProps): JSX.Element;
|
|
164
172
|
/** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
|
|
165
173
|
export declare function EmbedThemed({ accent, colorMode, font, variant, children, style, }: {
|
|
166
174
|
accent: AppAccent;
|
|
@@ -170,5 +178,5 @@ export declare function EmbedThemed({ accent, colorMode, font, variant, children
|
|
|
170
178
|
children: ReactNode;
|
|
171
179
|
style?: CSSProperties;
|
|
172
180
|
}): JSX.Element;
|
|
173
|
-
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
181
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, language, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
174
182
|
export default AppEmbed;
|