@datalayer/agent-runtimes 1.3.83 → 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.
Files changed (168) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +30 -11
  17. package/lib/chat/base/loopElement.d.ts +53 -0
  18. package/lib/chat/base/loopElement.js +78 -0
  19. package/lib/chat/base/loopWindow.d.ts +31 -0
  20. package/lib/chat/base/loopWindow.js +74 -0
  21. package/lib/chat/display/EmptyState.js +3 -1
  22. package/lib/chat/index.d.ts +2 -0
  23. package/lib/chat/index.js +3 -0
  24. package/lib/chat/messages/ChatMessages.js +3 -1
  25. package/lib/chat/messages/TurnFooter.js +3 -1
  26. package/lib/chat/prompt/InputPrompt.js +3 -1
  27. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  28. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  29. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  30. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  31. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  32. package/lib/chat/words.d.ts +102 -0
  33. package/lib/chat/words.js +460 -0
  34. package/lib/components/a2ui/custom/index.d.ts +39 -0
  35. package/lib/components/a2ui/custom/index.js +95 -0
  36. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  37. package/lib/components/a2ui/custom/sandbox.js +188 -0
  38. package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
  39. package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
  40. package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
  41. package/lib/components/a2ui/datalayer/Form.js +9 -2
  42. package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
  43. package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
  44. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/index.js +1 -1
  46. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  47. package/lib/components/a2ui/datalayer/schema.js +12 -1
  48. package/lib/components/a2ui/index.d.ts +1 -0
  49. package/lib/components/a2ui/index.js +1 -0
  50. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  51. package/lib/components/a2ui/visibleWhen.js +24 -3
  52. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  53. package/lib/components/inspector/AgentInspector.js +27 -215
  54. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  55. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  56. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  57. package/lib/components/inspector/a2aSpans.js +373 -0
  58. package/lib/components/inspector/agentSpans.d.ts +120 -0
  59. package/lib/components/inspector/agentSpans.js +253 -0
  60. package/lib/components/inspector/chatSpans.d.ts +36 -0
  61. package/lib/components/inspector/chatSpans.js +50 -0
  62. package/lib/components/inspector/classify.d.ts +11 -8
  63. package/lib/components/inspector/classify.js +23 -24
  64. package/lib/components/inspector/index.d.ts +7 -7
  65. package/lib/components/inspector/index.js +7 -7
  66. package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
  67. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  68. package/lib/components/teams/SceneTranscript.js +54 -0
  69. package/lib/components/teams/SceneView.d.ts +41 -0
  70. package/lib/components/teams/SceneView.js +28 -0
  71. package/lib/components/teams/index.d.ts +3 -0
  72. package/lib/components/teams/index.js +3 -0
  73. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  74. package/lib/components/teams/sceneTranscript.js +294 -0
  75. package/lib/components/teams/teamConnections.d.ts +2 -2
  76. package/lib/components/teams/teamConnections.js +3 -3
  77. package/lib/components/teams/useA2ATeam.d.ts +4 -4
  78. package/lib/components/teams/useA2ATeam.js +50 -46
  79. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  80. package/lib/examples/AgentA2ATeamExample.js +70 -60
  81. package/lib/examples/AssistantExample.js +2 -2
  82. package/lib/loop/apps/AppComposer.d.ts +40 -4
  83. package/lib/loop/apps/AppComposer.js +194 -19
  84. package/lib/loop/apps/AppRenderer.d.ts +21 -3
  85. package/lib/loop/apps/AppRenderer.js +88 -48
  86. package/lib/loop/apps/appspec.d.ts +13 -1
  87. package/lib/loop/apps/appspec.js +305 -12
  88. package/lib/loop/apps/checks.d.ts +14 -0
  89. package/lib/loop/apps/checks.js +276 -5
  90. package/lib/loop/apps/composer.d.ts +21 -10
  91. package/lib/loop/apps/composer.js +41 -8
  92. package/lib/loop/apps/customComponents.d.ts +41 -0
  93. package/lib/loop/apps/customComponents.js +260 -0
  94. package/lib/loop/apps/hostTools.d.ts +8 -1
  95. package/lib/loop/apps/hostTools.js +9 -0
  96. package/lib/loop/apps/index.d.ts +1 -0
  97. package/lib/loop/apps/index.js +1 -0
  98. package/lib/loop/apps/language.d.ts +68 -0
  99. package/lib/loop/apps/language.js +275 -0
  100. package/lib/loop/apps/settingsInputs.d.ts +29 -0
  101. package/lib/loop/apps/settingsInputs.js +147 -0
  102. package/lib/loop/apps/uploads.d.ts +43 -0
  103. package/lib/loop/apps/uploads.js +66 -0
  104. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  105. package/lib/loop/core/a2uiComponents.js +15 -0
  106. package/lib/loop/core/index.d.ts +15 -1
  107. package/lib/loop/core/index.js +3 -0
  108. package/lib/loop/embed/AppEmbed.d.ts +25 -3
  109. package/lib/loop/embed/AppEmbed.js +106 -18
  110. package/lib/loop/embed/element.d.ts +5 -0
  111. package/lib/loop/embed/element.js +84 -5
  112. package/lib/loop/embed/embedConfig.d.ts +8 -0
  113. package/lib/loop/embed/embedConfig.js +18 -0
  114. package/lib/loop/embed/embedSession.d.ts +14 -0
  115. package/lib/loop/embed/embedSession.js +34 -0
  116. package/lib/loop/embed/hostBridge.d.ts +50 -3
  117. package/lib/loop/embed/hostBridge.js +22 -2
  118. package/lib/loop/index.d.ts +4 -3
  119. package/lib/loop/index.js +4 -3
  120. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  121. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  122. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  123. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  124. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  125. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  126. package/lib/loop/plugins/app-components/index.js +56 -0
  127. package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
  128. package/lib/loop/plugins/app-elements/AppElements.js +79 -0
  129. package/lib/loop/plugins/app-elements/index.d.ts +20 -0
  130. package/lib/loop/plugins/app-elements/index.js +43 -0
  131. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  132. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  133. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  134. package/lib/loop/plugins/app-page/appPageModel.js +122 -4
  135. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  136. package/lib/loop/plugins/app-page/index.js +1 -0
  137. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  138. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  139. package/lib/loop/plugins/assistant/index.js +8 -6
  140. package/lib/loop/plugins/chat/ChatView.js +16 -6
  141. package/lib/loop/plugins/chat/index.d.ts +1 -1
  142. package/lib/loop/plugins/chat/index.js +2 -1
  143. package/lib/models/PageTag.d.ts +1 -1
  144. package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
  145. package/lib/protocols/BrowserAgentAdapter.js +13 -10
  146. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  147. package/lib/runtimes/browser/protocol.d.ts +1 -1
  148. package/lib/specs/actions.js +78 -1
  149. package/lib/specs/apps.d.ts +5 -0
  150. package/lib/specs/apps.js +1518 -216
  151. package/lib/specs/appspecSchema.js +630 -3
  152. package/lib/specs/teams/teams.d.ts +3 -0
  153. package/lib/specs/teams/teams.js +249 -0
  154. package/lib/specs/uiPlugins.js +6 -1
  155. package/lib/types/agentspecs.d.ts +200 -1
  156. package/lib/types/chat.d.ts +6 -5
  157. package/lib/types/teams.d.ts +13 -2
  158. package/package.json +2 -2
  159. package/scripts/codegen/generate_apps.py +20 -2
  160. package/scripts/codegen/generate_teams.py +15 -2
  161. package/lib/components/inspector/JsonTree.d.ts +0 -24
  162. package/lib/components/inspector/JsonTree.js +0 -89
  163. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  164. package/lib/components/inspector/a2aInspect.js +0 -478
  165. package/lib/components/inspector/agentInspector.d.ts +0 -179
  166. package/lib/components/inspector/agentInspector.js +0 -267
  167. package/lib/components/inspector/chatInspect.d.ts +0 -38
  168. package/lib/components/inspector/chatInspect.js +0 -65
@@ -0,0 +1,147 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The nine inputs a setting is drawn with (LOOP P-20): each a JSON Schema
7
+ * field and, where the field's own widget is not the one wanted, the widget a
8
+ * form's uiSchema names for it (`ui:widget`) — what `@datalayer/primer-rjsf`
9
+ * draws. agentspecs' `SETTING_INPUTS`, `FORM_WIDGETS` and
10
+ * `form_ui_problems`, said again for the page, in its sentences.
11
+ *
12
+ * Pure: no React, no network.
13
+ *
14
+ * @module loop/apps/settingsInputs
15
+ */
16
+ /** The nine inputs: the field each is, and the widget it is drawn with. */
17
+ export const SETTING_INPUTS = {
18
+ Select: { field: 'a field with an `enum`', widget: 'select (its own)' },
19
+ Slider: {
20
+ field: 'a `number` or an `integer` with a `minimum` and a `maximum`',
21
+ widget: 'range',
22
+ },
23
+ Switch: { field: 'a `boolean`', widget: 'switch' },
24
+ TextInput: { field: 'a `string`', widget: 'text (its own), or textarea' },
25
+ Checkbox: { field: 'a `boolean`', widget: 'checkbox (its own)' },
26
+ DatePicker: {
27
+ field: 'a `string` of `format: date`',
28
+ widget: 'date (its own)',
29
+ },
30
+ MultiSelect: {
31
+ field: 'an `array` of `uniqueItems` whose `items` have an `enum`',
32
+ widget: 'select (its own), or checkboxes',
33
+ },
34
+ RadioGroup: { field: 'a field with an `enum`', widget: 'radio' },
35
+ Tags: {
36
+ field: 'an `array` of `string` items without an `enum`',
37
+ widget: 'tags',
38
+ },
39
+ };
40
+ /** The widgets a form's `ui` may name (`ui:widget`). */
41
+ export const FORM_WIDGETS = [
42
+ 'select',
43
+ 'radio',
44
+ 'range',
45
+ 'updown',
46
+ 'switch',
47
+ 'checkbox',
48
+ 'text',
49
+ 'textarea',
50
+ 'date',
51
+ 'checkboxes',
52
+ 'tags',
53
+ ];
54
+ const isData = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
55
+ /** Why a widget cannot draw a field, in words; null when it can. */
56
+ export function widgetRefusal(widget, field) {
57
+ const kind = field.type;
58
+ const items = isData(field.items) ? field.items : {};
59
+ switch (widget) {
60
+ case 'select':
61
+ case 'radio':
62
+ if ('enum' in field || (widget === 'radio' && kind === 'boolean')) {
63
+ return null;
64
+ }
65
+ if (widget === 'select' && kind === 'array' && 'enum' in items) {
66
+ return null;
67
+ }
68
+ return 'draws a choice: the field has an `enum`';
69
+ case 'range':
70
+ return (kind === 'number' || kind === 'integer') &&
71
+ 'minimum' in field &&
72
+ 'maximum' in field
73
+ ? null
74
+ : 'is a slider: a `number` or an `integer` with a `minimum` and a `maximum`';
75
+ case 'updown':
76
+ return kind === 'number' || kind === 'integer'
77
+ ? null
78
+ : 'draws a `number` or an `integer`';
79
+ case 'switch':
80
+ case 'checkbox':
81
+ return kind === 'boolean' ? null : 'draws a `boolean`';
82
+ case 'text':
83
+ case 'textarea':
84
+ return kind === 'string' ? null : 'draws a `string`';
85
+ case 'date':
86
+ return kind === 'string' ? null : 'draws a `string` of `format: date`';
87
+ case 'checkboxes':
88
+ return kind === 'array' && 'enum' in items
89
+ ? null
90
+ : 'draws an `array` whose `items` have an `enum`';
91
+ case 'tags':
92
+ return kind === 'array' && items.type === 'string' && !('enum' in items)
93
+ ? null
94
+ : 'draws an `array` of `string` items without an `enum`';
95
+ default:
96
+ return `is no widget: ${FORM_WIDGETS.join(', ')}`;
97
+ }
98
+ }
99
+ const quoted = (value) => `“${String(value)}”`;
100
+ /**
101
+ * What is wrong with how a form's fields are drawn, in agentspecs'
102
+ * `form_ui_problems` sentences: by field name, the `ui:` options of each —
103
+ * `ui:widget` one of {@link FORM_WIDGETS} that draws the field — and
104
+ * `ui:order` at its top.
105
+ */
106
+ export function formUiProblems(said, schema, ui) {
107
+ if (!isData(ui)) {
108
+ return [`${said} is drawn by field name: its \`ui\` is a mapping.`];
109
+ }
110
+ const fields = isData(schema.properties) ? schema.properties : {};
111
+ const problems = [];
112
+ for (const [name, options] of Object.entries(ui)) {
113
+ if (name === 'ui:order') {
114
+ const order = Array.isArray(options) ? options : null;
115
+ const unknown = (order ?? []).filter(item => item !== '*' && !(String(item) in fields));
116
+ if (order === null || unknown.length > 0) {
117
+ problems.push(`${said} orders fields it does not ask: ${unknown.map(quoted).join(', ') || String(options)}.`);
118
+ }
119
+ continue;
120
+ }
121
+ if (name.startsWith('ui:')) {
122
+ continue;
123
+ }
124
+ if (!(name in fields)) {
125
+ problems.push(`${said} says how to draw ${quoted(name)}, which it does not ask.`);
126
+ continue;
127
+ }
128
+ if (!isData(options)) {
129
+ problems.push(`${said}'s field ${quoted(name)} is drawn with \`ui:\` options, a mapping.`);
130
+ continue;
131
+ }
132
+ const wrong = Object.keys(options).filter(key => !key.startsWith('ui:') && key !== 'items');
133
+ if (wrong.length > 0) {
134
+ problems.push(`${said}'s field ${quoted(name)} is drawn with \`ui:\` options, not ${wrong.map(quoted).join(', ')}.`);
135
+ }
136
+ const widget = options['ui:widget'];
137
+ const field = fields[name];
138
+ if (widget === undefined || !isData(field)) {
139
+ continue;
140
+ }
141
+ const refusal = widgetRefusal(String(widget), field);
142
+ if (refusal) {
143
+ problems.push(`${said}'s field ${quoted(name)} is drawn with ${quoted(widget)}, which ${refusal}.`);
144
+ }
145
+ }
146
+ return problems;
147
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * What a person sends (LOOP P-21), as rules: the kinds of file an
3
+ * application takes in its composer without asking (`interface.uploads`),
4
+ * each with its largest size, and how many at once — and why a file is
5
+ * refused, in the sentence the runtime says when it was sent all the same.
6
+ * agentspecs' own (`AppUploads.refusal`, `too_many`, `upload_kind_takes`),
7
+ * said again for the page.
8
+ *
9
+ * Pure: no React, no network.
10
+ *
11
+ * @module loop/apps/uploads
12
+ */
13
+ import type { AppSpec, AppUploadsSpec } from '../../types/agentspecs';
14
+ /** The largest file a person may send an application, in megabytes. */
15
+ export declare const MAX_UPLOAD_MB = 25;
16
+ /** What a kind takes when it does not say, in megabytes. */
17
+ export declare const DEFAULT_UPLOAD_MB = 10;
18
+ /** How many files go with one message when it does not say. */
19
+ export declare const DEFAULT_MAX_FILES = 5;
20
+ /** A media type, a family of them, or an extension, lowercase. */
21
+ export declare const UPLOAD_KIND: RegExp;
22
+ /** A file as the person gives it: its name, its type, its size in bytes. */
23
+ export type SentFile = {
24
+ name: string;
25
+ type: string;
26
+ size: number;
27
+ };
28
+ /**
29
+ * Whether a kind of file takes a file: an extension (`.csv`) by its name, a
30
+ * media type (`application/pdf`) or a family (`image/*`) by its type.
31
+ */
32
+ export declare function kindTakes(kind: string, file: Pick<SentFile, 'name' | 'type'>): boolean;
33
+ /** Whether any of `kinds` takes a file; any file when there are none. */
34
+ export declare const acceptsFile: (kinds: readonly string[] | undefined, file: Pick<SentFile, "name" | "type">) => boolean;
35
+ /** The `accept` of a file input for what the application takes. */
36
+ export declare const uploadsAccept: (uploads: AppUploadsSpec) => string;
37
+ /**
38
+ * Why files sent without being asked are refused, in a sentence — the
39
+ * runtime's — or `null` when the application takes every one of them.
40
+ */
41
+ export declare function uploadsRefusal(app: Pick<AppSpec, 'name' | 'interface'>, files: readonly SentFile[]): string | null;
42
+ /** What the composer says it takes, in a sentence. */
43
+ export declare function uploadsInWords(uploads: AppUploadsSpec): string;
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** The largest file a person may send an application, in megabytes. */
6
+ export const MAX_UPLOAD_MB = 25;
7
+ /** What a kind takes when it does not say, in megabytes. */
8
+ export const DEFAULT_UPLOAD_MB = 10;
9
+ /** How many files go with one message when it does not say. */
10
+ export const DEFAULT_MAX_FILES = 5;
11
+ /** A media type, a family of them, or an extension, lowercase. */
12
+ export const UPLOAD_KIND = /^(?:[a-z0-9][a-z0-9.+-]*\/(?:\*|[a-z0-9][a-z0-9.+-]*)|\.[a-z0-9][a-z0-9_+-]*)$/;
13
+ /**
14
+ * Whether a kind of file takes a file: an extension (`.csv`) by its name, a
15
+ * media type (`application/pdf`) or a family (`image/*`) by its type.
16
+ */
17
+ export function kindTakes(kind, file) {
18
+ const wanted = kind.trim().toLowerCase();
19
+ if (wanted.startsWith('.')) {
20
+ return file.name.toLowerCase().endsWith(wanted);
21
+ }
22
+ const type = (file.type || '').split(';', 1)[0].trim().toLowerCase();
23
+ if (wanted.endsWith('/*')) {
24
+ return type.startsWith(wanted.slice(0, -1));
25
+ }
26
+ return type === wanted;
27
+ }
28
+ /** Whether any of `kinds` takes a file; any file when there are none. */
29
+ export const acceptsFile = (kinds, file) => !kinds?.length || kinds.some(kind => kindTakes(kind, file));
30
+ const megabytes = (size) => `${(size / (1024 * 1024)).toFixed(1)} MB`;
31
+ /** The `accept` of a file input for what the application takes. */
32
+ export const uploadsAccept = (uploads) => uploads.kinds.map(kind => kind.type).join(',');
33
+ /**
34
+ * Why files sent without being asked are refused, in a sentence — the
35
+ * runtime's — or `null` when the application takes every one of them.
36
+ */
37
+ export function uploadsRefusal(app, files) {
38
+ if (files.length === 0) {
39
+ return null;
40
+ }
41
+ const uploads = app.interface.uploads;
42
+ if (!uploads) {
43
+ return `${app.name} takes no file sent with a message: its Appspec names none it takes (interface.uploads).`;
44
+ }
45
+ if (files.length > uploads.maxFiles) {
46
+ return `${files.length} files were sent at once: ${app.name} takes at most ${uploads.maxFiles}.`;
47
+ }
48
+ for (const file of files) {
49
+ const kind = uploads.kinds.find(candidate => kindTakes(candidate.type, file));
50
+ if (!kind) {
51
+ const taken = uploads.kinds.map(candidate => candidate.type).join(', ');
52
+ return `${file.name} is not a kind of file ${app.name} takes: it takes ${taken}.`;
53
+ }
54
+ if (file.size > kind.maxMb * 1024 * 1024) {
55
+ return `${file.name} is ${megabytes(file.size)}: ${app.name} takes ${kind.type} of at most ${kind.maxMb} MB.`;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ /** What the composer says it takes, in a sentence. */
61
+ export function uploadsInWords(uploads) {
62
+ const kinds = uploads.kinds
63
+ .map(kind => `${kind.type} up to ${kind.maxMb} MB`)
64
+ .join(', ');
65
+ return `Takes ${kinds}; ${uploads.maxFiles} at most at once.`;
66
+ }
@@ -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');
@@ -519,7 +519,7 @@ export type AgentBlueprintContribution = {
519
519
  * P-19) tell a run turned in the page, and the model it runs on — as the
520
520
  * runtime's session API applies them. Unsaid for an agent with no modes.
521
521
  */
522
- modeEffect?: (chosen: Record<string, string>) => AppModeEffect;
522
+ modeEffect?: (chosen: Record<string, string>, profile?: string) => AppModeEffect;
523
523
  };
524
524
  export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
525
525
  /** One conversation opener a plugin offers on the empty chat. */
@@ -529,6 +529,12 @@ export type ChatSuggestionItem = {
529
529
  /** What is submitted; the chip's words when absent. */
530
530
  message?: string;
531
531
  emoji?: string;
532
+ /**
533
+ * The heading it is offered under: openers of one group together, under
534
+ * its name, those without one first — an application's starter's category
535
+ * (LOOP P-20).
536
+ */
537
+ group?: string;
532
538
  };
533
539
  /**
534
540
  * Conversation openers, contributed ahead of the spec's own.
@@ -573,6 +579,7 @@ export type AssistantMenuContribution = {
573
579
  };
574
580
  export declare const LoopAssistantMenu: import("@datalayer/reactor").ContributionPoint<AssistantMenuContribution>;
575
581
  export { LoopCanvasBlock, type CanvasBlockContribution } from './canvasBlocks';
582
+ export { LoopA2uiComponent, type A2uiComponentContribution, } from './a2uiComponents';
576
583
  /**
577
584
  * The per-example chat extras a host feeds the loop's conversation live.
578
585
  *
@@ -649,6 +656,13 @@ export type LoopChatExtrasValue = {
649
656
  * offering a first question to somebody who has already asked one.
650
657
  */
651
658
  hideOpeners?: boolean;
659
+ /**
660
+ * Openers in place of the contributed ones, live: what an application
661
+ * offers with the profile picked in its composer (LOOP P-20) — the empty
662
+ * chat offers these, and changes them when the profile changes, without
663
+ * rebuilding the reactor. `hiddenOpeners` still applies.
664
+ */
665
+ openers?: ChatSuggestionItem[];
652
666
  /**
653
667
  * Told what the agent has used, each time the chat learns it.
654
668
  *
@@ -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 { PluginRef } from '@datalayer/reactor';
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
@@ -91,6 +97,20 @@ export declare function useEmbedSession({ app, serverUrl, embedToken, resume, }:
91
97
  embedToken?: string;
92
98
  resume?: boolean;
93
99
  }): EmbedSession;
100
+ /**
101
+ * Window messages from the page (LOOP P-25): on the host's own server, the
102
+ * page is handed how to post one to the application's session
103
+ * (`host.onWindowPort`) — the session the chat goes on with, its uid known
104
+ * from its first message — and what the code answers is raised on the page
105
+ * as `window-message` events. The session returned is the one to draw: its
106
+ * thread told when it starts.
107
+ */
108
+ export declare function useWindowPort({ serverUrl, embedToken, session, host, }: {
109
+ serverUrl?: string;
110
+ embedToken?: string;
111
+ session: EmbedSession;
112
+ host?: AppEmbedHost;
113
+ }): EmbedSession;
94
114
  /**
95
115
  * The page and the application talking (LOOP D-10): the tools of the host
96
116
  * the Appspec names, given to its agent through a chat-extras plugin made
@@ -136,6 +156,8 @@ export type AppFloatingProps = {
136
156
  host?: AppEmbedHost;
137
157
  /** The visit's session, picked up again after a reload (D-13). */
138
158
  session?: EmbedSession;
159
+ /** The language the visitor reads it in (P-26); the browser's by default. */
160
+ language?: string;
139
161
  };
140
162
  /**
141
163
  * The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
@@ -146,7 +168,7 @@ export type AppFloatingProps = {
146
168
  * The workspace tells the chrome what the application is doing and what it
147
169
  * last said (`onPresence`, `onSaying`).
148
170
  */
149
- 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;
150
172
  /** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
151
173
  export declare function EmbedThemed({ accent, colorMode, font, variant, children, style, }: {
152
174
  accent: AppAccent;
@@ -156,5 +178,5 @@ export declare function EmbedThemed({ accent, colorMode, font, variant, children
156
178
  children: ReactNode;
157
179
  style?: CSSProperties;
158
180
  }): JSX.Element;
159
- 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;
160
182
  export default AppEmbed;
@@ -40,6 +40,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
40
40
  * @module loop/embed/AppEmbed
41
41
  */
42
42
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
43
+ import { contribution, definePlugin } from '@datalayer/reactor';
43
44
  import { Text, registerPortalRoot } from '@primer/react';
44
45
  import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
45
46
  import { FluentEmoji } from '@datalayer/core/lib/components/emoji';
@@ -50,9 +51,12 @@ import { AppRenderer, } from '../apps/AppRenderer';
50
51
  import { AssistantCharactersPlugin, assistantCharacterFor, assistantCharactersFrom, } from '../plugins/assistant-characters';
51
52
  import { floatingViewOf } from './embedConfig';
52
53
  import { createChatExtrasPlugin } from '../plugins/chat-extras';
53
- import { hostFrontendTools } from './hostBridge';
54
+ import { hostFrontendTools, hostUserRunProps, } from './hostBridge';
55
+ import { LoopRunProps } from '../core';
54
56
  import { embedThemeOverrides, embedThemeStyles } from './embedTheme';
55
- import { embedSessionKey, reattachSession, sessionGoneSentence, sessionKeeper, visitOfToken, } from './embedSession';
57
+ import { WINDOW_NOT_YET, embedSessionKey, postWindowMessage, reattachSession, sessionGoneSentence, sessionKeeper, visitOfToken, } from './embedSession';
58
+ import { onLoopWindowMessage } from '../../chat/base/loopWindow';
59
+ import { ChatLanguage, useChatWords } from '../../chat/ChatLanguage';
56
60
  /**
57
61
  * The visit's session, kept and picked up again (LOOP D-13): on the host's
58
62
  * server with an embed token that names its visit, the session the chat
@@ -120,6 +124,55 @@ export function useEmbedSession({ app, serverUrl, embedToken, resume = true, })
120
124
  ...(found.said ? { said: found.said } : {}),
121
125
  };
122
126
  }
127
+ /**
128
+ * Window messages from the page (LOOP P-25): on the host's own server, the
129
+ * page is handed how to post one to the application's session
130
+ * (`host.onWindowPort`) — the session the chat goes on with, its uid known
131
+ * from its first message — and what the code answers is raised on the page
132
+ * as `window-message` events. The session returned is the one to draw: its
133
+ * thread told when it starts.
134
+ */
135
+ export function useWindowPort({ serverUrl, embedToken, session, host, }) {
136
+ const hostRef = useRef(host);
137
+ hostRef.current = host;
138
+ const given = session.thread;
139
+ const uid = useRef(given?.messages.length ? given.id : undefined);
140
+ // A thread of its own where the embed keeps none, so that its uid is known.
141
+ const own = useMemo(() => ({ id: generateMessageId(), messages: [] }), []);
142
+ const base = given ?? (serverUrl && session.ready ? own : undefined);
143
+ const thread = useMemo(() => base && {
144
+ ...base,
145
+ onStarted: (id) => {
146
+ uid.current = id;
147
+ base.onStarted?.(id);
148
+ },
149
+ }, [base]);
150
+ useEffect(() => {
151
+ const port = host?.onWindowPort;
152
+ if (!serverUrl || !port) {
153
+ return undefined;
154
+ }
155
+ port(async (data) => {
156
+ if (!uid.current) {
157
+ throw new Error(WINDOW_NOT_YET);
158
+ }
159
+ const said = await postWindowMessage({
160
+ serverUrl,
161
+ uid: uid.current,
162
+ ...(embedToken ? { token: embedToken } : {}),
163
+ data,
164
+ });
165
+ for (const answer of said) {
166
+ hostRef.current?.onEvent?.({
167
+ type: 'window-message',
168
+ detail: { data: answer },
169
+ });
170
+ }
171
+ });
172
+ return () => port(null);
173
+ }, [serverUrl, embedToken, host]);
174
+ return thread ? { ...session, thread } : session;
175
+ }
123
176
  /** What the embed says once of its session, above the conversation. */
124
177
  function SessionSaid({ said }) {
125
178
  return said ? (_jsx(Text, { as: "p", role: "status", sx: { px: 3, py: 2, m: 0, fontSize: 1 }, children: said })) : null;
@@ -145,7 +198,34 @@ export function useHostBridge(app, host) {
145
198
  // The tools change with what the Appspec names, and the rules that decide them.
146
199
  // eslint-disable-next-line react-hooks/exhaustive-deps
147
200
  }, [handle, key]);
148
- const plugins = useMemo(() => (bridge ? [handle.plugin] : NO_HOST_PLUGINS), [bridge, handle]);
201
+ // Who its user is (D-21): the token the host's server signed goes with
202
+ // every run, for an application that takes only a signed user.
203
+ const signed = bridge?.user === 'signed';
204
+ const userPlugin = useMemo(() => {
205
+ const runProps = hostUserRunProps(app, () => hostRef.current ?? {});
206
+ return runProps
207
+ ? definePlugin({
208
+ name: `loop-host-user-${app.id}`,
209
+ displayName: `${app.name}: who its user is`,
210
+ description: `The token naming its user that the host's server signed, sent with every run of ${app.name}.`,
211
+ octicon: 'person',
212
+ emoji: '\u{1F464}',
213
+ contributes: [
214
+ contribution(LoopRunProps, runProps, { id: 'host-user' }),
215
+ ],
216
+ })
217
+ : undefined;
218
+ // Made again only when whether it is signed changes.
219
+ // eslint-disable-next-line react-hooks/exhaustive-deps
220
+ }, [app.id, signed]);
221
+ const plugins = useMemo(() => bridge
222
+ ? [handle.plugin, ...(userPlugin ? [userPlugin] : [])]
223
+ : NO_HOST_PLUGINS, [bridge, handle, userPlugin]);
224
+ // What its code tells the page during a turn (LOOP P-25), raised on it.
225
+ useEffect(() => onLoopWindowMessage(data => hostRef.current?.onEvent?.({
226
+ type: 'window-message',
227
+ detail: { data },
228
+ })), []);
149
229
  const said = useRef({ answering: false });
150
230
  const before = useRef('idle');
151
231
  const told = useRef('');
@@ -199,7 +279,8 @@ function Face({ emoji }) {
199
279
  * The workspace tells the chrome what the application is doing and what it
200
280
  * last said (`onPresence`, `onSaying`).
201
281
  */
202
- export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }) {
282
+ export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, language, }) {
283
+ const chatText = useChatWords();
203
284
  const [presence, setPresence] = useState('idle');
204
285
  const [said, setSaid] = useState({ answering: false });
205
286
  const bridge = useHostBridge(app, host);
@@ -219,7 +300,7 @@ export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides
219
300
  const signedOut = !serverUrl && !token && !renderer?.datalayerVisitors;
220
301
  return (_jsx(ChatFloating, { defaultViewMode: view, ...(character ? { assistantCharacter: character } : {}), ...(app.interface.balloon
221
302
  ? { balloonDisplay: app.interface.balloon }
222
- : {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: `Talk to ${app.name}`, colorMode: colorMode, useStore: false, conversation: {
303
+ : {}), title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: chatText.talkTo(app.name), colorMode: colorMode, useStore: false, conversation: {
223
304
  body: signedOut ? (_jsx(Text, { as: "p", role: "status", sx: { p: 3, m: 0, fontSize: 1 }, children: signedOutSentence(app) })) : session && !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session?.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
224
305
  // The host's accent and face over the application's own, inside
225
306
  // its conversation too, in the embed's mode.
@@ -227,7 +308,7 @@ export function AppFloating({ app, view, colorMode, themeVariant, themeOverrides
227
308
  // The window draws its face, its name and close.
228
309
  hideChatHeader: true,
229
310
  // Mounted closed: the caret goes in when the window opens.
230
- autoFocusPrompt: false, onPresence: onPresence, onSaying: onSaying, plugins: bridge.plugins, ...(session?.thread ? { thread: session.thread } : {}), ...renderer })] })),
311
+ autoFocusPrompt: false, onPresence: onPresence, onSaying: onSaying, plugins: bridge.plugins, ...(session?.thread ? { thread: session.thread } : {}), ...(language ? { language } : {}), ...renderer })] })),
231
312
  presence,
232
313
  saying: said.saying,
233
314
  answering: said.answering,
@@ -276,9 +357,16 @@ export function EmbedThemed({ accent, colorMode, font, variant, children, style,
276
357
  */
277
358
  _jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: getThemeConfig(variant).primerTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
278
359
  }
279
- export function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, host, resume = true, }) {
360
+ export function AppEmbed({ app, mode, accent, colorMode, font, language, serverUrl, instance, embedToken, height = 640, ownPortal = false, plugins = NO_PLUGINS, host, resume = true, }) {
280
361
  const bridge = useHostBridge(app, host);
281
- const session = useEmbedSession({ app, serverUrl, embedToken, resume });
362
+ const kept = useEmbedSession({ app, serverUrl, embedToken, resume });
363
+ // Window messages from the page, on the host's own server (P-25).
364
+ const session = useWindowPort({
365
+ serverUrl,
366
+ embedToken,
367
+ session: kept,
368
+ host,
369
+ });
282
370
  const system = useSystemMode();
283
371
  // The theme the application names (T-30), `loop` when none; its mode
284
372
  // the host's, else the application's, else the visitor's system's.
@@ -297,15 +385,15 @@ export function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instan
297
385
  return (_jsx("p", { role: "status", className: "datalayer-app-said", children: assistant.problem }));
298
386
  }
299
387
  const character = assistant?.character;
300
- return (_jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, variant: variant, style: view
301
- ? // Floating: nothing in the page's flow, nothing painted behind.
302
- { backgroundColor: 'transparent' }
303
- : { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(AppFloating, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeVariant: variant, themeOverrides: themeOverrides, character: character, host: host, session: session })) : !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
304
- // The host's accent and face over the application's own, inside
305
- // its conversation too, in the embed's mode: the host's or its
306
- // visitor's system's, not a Datalayer setting.
307
- themeOverrides: themeOverrides, colorMode: resolvedMode, themeVariant: variant,
308
- // What the page gives it, and what it tells the page (D-10).
309
- plugins: bridge.plugins, onPresence: bridge.onPresence, onSaying: bridge.onSaying, ...(session.thread ? { thread: session.thread } : {}) })] }))] }));
388
+ return (_jsx(ChatLanguage, { language: language, children: _jsxs(EmbedThemed, { accent: worn, colorMode: resolvedMode, font: font, variant: variant, style: view
389
+ ? // Floating: nothing in the page's flow, nothing painted behind.
390
+ { backgroundColor: 'transparent' }
391
+ : { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view ? (_jsx(AppFloating, { app: app, view: view, serverUrl: serverUrl, instance: instance, embedToken: embedToken, colorMode: resolvedMode, themeVariant: variant, themeOverrides: themeOverrides, character: character, host: host, session: session, ...(language ? { language } : {}) })) : !session.ready ? null : (_jsxs(_Fragment, { children: [_jsx(SessionSaid, { said: session.said }), _jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), ...(serverUrl && embedToken ? { embedToken } : {}), instance: instance,
392
+ // The host's accent and face over the application's own, inside
393
+ // its conversation too, in the embed's mode: the host's or its
394
+ // visitor's system's, not a Datalayer setting.
395
+ themeOverrides: themeOverrides, colorMode: resolvedMode, themeVariant: variant,
396
+ // What the page gives it, and what it tells the page (D-10).
397
+ plugins: bridge.plugins, onPresence: bridge.onPresence, onSaying: bridge.onSaying, ...(session.thread ? { thread: session.thread } : {}), ...(language ? { language } : {}) })] }))] }) }));
310
398
  }
311
399
  export default AppEmbed;
@@ -31,6 +31,11 @@ export declare function appspecOfText(text: string): AppSpec;
31
31
  export declare function runnable({ app, problems }: ParsedAppspec): AppSpec;
32
32
  /** The run page an application on Datalayer is framed from. */
33
33
  export declare function frameSrcOf(origin: string, app: string, token?: string): string;
34
+ /**
35
+ * A framed run page told the visitor's language (P-26): `language=` in its
36
+ * query, before the fragment that holds the token.
37
+ */
38
+ export declare function frameInLanguage(src: string, language?: string): string;
34
39
  /**
35
40
  * An application on Datalayer, read with its embed token: what the space
36
41
  * keeps of it (`model_s`, the stored definition), whose `spec` is the