@datalayer/agent-runtimes 1.3.28 → 1.3.30

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 (99) hide show
  1. package/lib/AgentNode.js +14 -33
  2. package/lib/chat/ChatFloating.d.ts +12 -2
  3. package/lib/chat/ChatFloating.js +196 -25
  4. package/lib/chat/assistant/AssistantStage.d.ts +70 -0
  5. package/lib/chat/assistant/AssistantStage.js +192 -0
  6. package/lib/chat/assistant/SpeechBalloon.d.ts +26 -0
  7. package/lib/chat/assistant/SpeechBalloon.js +46 -0
  8. package/lib/chat/assistant/SpriteCharacter.d.ts +26 -0
  9. package/lib/chat/assistant/SpriteCharacter.js +73 -0
  10. package/lib/chat/assistant/characters.d.ts +30 -0
  11. package/lib/chat/assistant/characters.js +41 -0
  12. package/lib/chat/assistant/formats/acs.d.ts +88 -0
  13. package/lib/chat/assistant/formats/acs.js +493 -0
  14. package/lib/chat/assistant/formats/agentCompression.d.ts +5 -0
  15. package/lib/chat/assistant/formats/agentCompression.js +92 -0
  16. package/lib/chat/assistant/formats/blobs.d.ts +6 -0
  17. package/lib/chat/assistant/formats/blobs.js +52 -0
  18. package/lib/chat/assistant/formats/clippy.d.ts +21 -0
  19. package/lib/chat/assistant/formats/clippy.js +167 -0
  20. package/lib/chat/assistant/formats/index.d.ts +13 -0
  21. package/lib/chat/assistant/formats/index.js +15 -0
  22. package/lib/chat/assistant/formats/objectLiteral.d.ts +23 -0
  23. package/lib/chat/assistant/formats/objectLiteral.js +233 -0
  24. package/lib/chat/assistant/formats/stateAnimations.d.ts +16 -0
  25. package/lib/chat/assistant/formats/stateAnimations.js +115 -0
  26. package/lib/chat/assistant/formats/types.d.ts +56 -0
  27. package/lib/chat/assistant/formats/types.js +11 -0
  28. package/lib/chat/assistant/state.d.ts +62 -0
  29. package/lib/chat/assistant/state.js +116 -0
  30. package/lib/chat/base/ChatBase.js +41 -32
  31. package/lib/chat/base/modelChoice.d.ts +13 -0
  32. package/lib/chat/base/modelChoice.js +13 -0
  33. package/lib/chat/header/ChatViewModeToggle.js +6 -3
  34. package/lib/{loop/plugins/chat → chat/presence}/Presence.js +1 -1
  35. package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.d.ts +7 -1
  36. package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.js +18 -0
  37. package/lib/chat/viewModes.d.ts +6 -0
  38. package/lib/chat/viewModes.js +10 -1
  39. package/lib/examples/ChatAssistantExample.d.ts +8 -0
  40. package/lib/examples/ChatAssistantExample.js +59 -0
  41. package/lib/examples/example-selector.js +1 -0
  42. package/lib/examples/main.js +3 -0
  43. package/lib/loop/apps/AppRenderer.d.ts +34 -2
  44. package/lib/loop/apps/AppRenderer.js +96 -29
  45. package/lib/loop/apps/agent.d.ts +15 -0
  46. package/lib/loop/apps/agent.js +42 -0
  47. package/lib/loop/apps/appspec.d.ts +5 -1
  48. package/lib/loop/apps/appspec.js +21 -1
  49. package/lib/loop/apps/checks.js +3 -1
  50. package/lib/loop/apps/index.d.ts +1 -0
  51. package/lib/loop/apps/index.js +1 -0
  52. package/lib/loop/core/index.d.ts +23 -0
  53. package/lib/loop/core/index.js +1 -0
  54. package/lib/loop/embed/AppEmbed.d.ts +42 -0
  55. package/lib/loop/embed/AppEmbed.js +205 -0
  56. package/lib/loop/embed/datalayer-app.d.ts +1 -0
  57. package/lib/loop/embed/datalayer-app.js +16 -0
  58. package/lib/loop/embed/element.d.ts +57 -0
  59. package/lib/loop/embed/element.js +405 -0
  60. package/lib/loop/embed/embedConfig.d.ts +120 -0
  61. package/lib/loop/embed/embedConfig.js +148 -0
  62. package/lib/loop/embed/embedTheme.d.ts +24 -0
  63. package/lib/loop/embed/embedTheme.js +83 -0
  64. package/lib/loop/index.d.ts +6 -2
  65. package/lib/loop/index.js +8 -2
  66. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +21 -3
  67. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +34 -6
  68. package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
  69. package/lib/loop/plugins/agent-capacity/index.js +3 -1
  70. package/lib/loop/plugins/agents/switchable.d.ts +7 -0
  71. package/lib/loop/plugins/agents/switchable.js +91 -88
  72. package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
  73. package/lib/loop/plugins/app-page/AppPage.js +53 -0
  74. package/lib/loop/plugins/app-page/appPage.d.ts +180 -0
  75. package/lib/loop/plugins/app-page/appPage.js +443 -0
  76. package/lib/loop/plugins/app-page/index.d.ts +8 -0
  77. package/lib/loop/plugins/app-page/index.js +34 -0
  78. package/lib/loop/plugins/assistant-characters/index.d.ts +30 -0
  79. package/lib/loop/plugins/assistant-characters/index.js +41 -0
  80. package/lib/loop/plugins/chat/ChatView.js +50 -39
  81. package/lib/loop/plugins/chat/index.d.ts +1 -1
  82. package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
  83. package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
  84. package/lib/loop/plugins/page-layout/index.d.ts +13 -0
  85. package/lib/loop/plugins/page-layout/index.js +15 -2
  86. package/lib/loop/presets.d.ts +12 -0
  87. package/lib/loop/presets.js +5 -3
  88. package/lib/specs/actions.js +14 -0
  89. package/lib/specs/agents/agents.js +4 -0
  90. package/lib/specs/apps.d.ts +13 -1
  91. package/lib/specs/apps.js +2715 -373
  92. package/lib/specs/appspecSchema.js +21 -3
  93. package/lib/types/agentspecs.d.ts +21 -1
  94. package/lib/types/chat.d.ts +1 -1
  95. package/lib/types/config.d.ts +12 -0
  96. package/package.json +2 -1
  97. package/scripts/codegen/generate_agents.py +50 -2
  98. package/scripts/codegen/generate_apps.py +36 -6
  99. /package/lib/{loop/plugins/chat → chat/presence}/Presence.d.ts +0 -0
@@ -16,13 +16,26 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
16
16
  * created with the application in its payload — so the runtime reaches only
17
17
  * what the application connects to and enforces its rules before every tool
18
18
  * call — and the application's starters as the openers of the empty chat;
19
- * - the **layout of its kind**: a chat is the conversation alone, with the
20
- * A2UI surfaces its answers draw; the other layouts keep the editors.
19
+ * - its **layout**, as `interface.layout` says (`appLayoutOptions`, LOOP
20
+ * T-07), on the workspace's own `page-layout` plugin: `chat` is the
21
+ * conversation alone, with the A2UI surfaces its answers draw; `page` is
22
+ * the page on a sheet with the composer over it and the conversation in a
23
+ * panel the composer's display modes open; `split` is the conversation and
24
+ * the page side by side, a hairline between them to drag;
25
+ * - its **page**, when it has one (`hasAppPage`: a chat, a widget or a worker
26
+ * with a `page` or `split` layout): its A2UI surface drawn by the
27
+ * `app-page` plugin, in place of the notebook and the document, fed from
28
+ * the conversation and answered through it (`APP_KIND_PATHS`). A
29
+ * decision's page stays the Studio's run page (R-02); its layout keeps the
30
+ * editors;
31
+ * - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
32
+ * window, its title the application's face and name.
21
33
  *
22
34
  * @module loop/apps/AppRenderer
23
35
  */
24
36
  import { useMemo } from 'react';
25
37
  import { defineAgentCapacityPlugin } from '../plugins/agent-capacity';
38
+ import { APP_PAGE_SURFACE, defineAppPagePlugin, hasAppPage, } from '../plugins/app-page';
26
39
  import { LoopEmbed } from '../embed/LoopEmbed';
27
40
  import { dumpAppspec } from './appspec';
28
41
  /** The id of an agent or a Cog, without its version. */
@@ -64,8 +77,75 @@ export function defineAppPlugin(app) {
64
77
  text: starter.label,
65
78
  message: starter.message,
66
79
  })),
80
+ // In the page, the agent is told what a runtime tells it: its own
81
+ // spec's prompt, then the application's instructions; on the
82
+ // application's model when it names one (`loop/apps/agent`).
83
+ instructions: app.instructions,
84
+ model: app.model,
67
85
  });
68
86
  }
87
+ /**
88
+ * The workspace an application's layout asks for (LOOP T-07).
89
+ *
90
+ * - `chat`: the conversation alone — no editor, no page, no strip.
91
+ * - `page`: the page layout — its page (or, without one, the editors) on the
92
+ * sheet; the composer a card over it, starting at the bottom, whose display
93
+ * modes put it in the conversation panel beside, over or in the corner of
94
+ * the page; the current turn under the composer, its copy and dismiss only.
95
+ * - `split`: the page layout's split — the conversation on the left with its
96
+ * composer, the page on the right, a hairline between them to drag.
97
+ *
98
+ * With a page, the page is the one editor, opened, without a strip to swap
99
+ * it; without one (a decision, whose page is the Studio's), the editors stay.
100
+ */
101
+ export function appLayoutOptions(app) {
102
+ const layout = app.interface.layout;
103
+ if (layout === 'chat') {
104
+ return { editors: false, showViewSelector: false };
105
+ }
106
+ const withPage = hasAppPage(app);
107
+ return {
108
+ editors: !withPage,
109
+ showViewSelector: !withPage,
110
+ ...(withPage ? { defaultEditor: APP_PAGE_SURFACE } : {}),
111
+ pageLayout: true,
112
+ pageLayoutArrangement: layout === 'split' ? 'split' : 'page',
113
+ pageLayoutPrompt: 'floating',
114
+ pageLayoutPromptAnchor: 'bottom',
115
+ pageLayoutTurnPanelFooter: 'actions',
116
+ };
117
+ }
118
+ /**
119
+ * What an application's agent is created with on a Datalayer runtime: under
120
+ * the application's id, over AG-UI, with the application's own document —
121
+ * so that the runtime registers it and decides every tool call by its rules
122
+ * (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
123
+ * place it runs: the workspace (`AppRenderer`) and the embed's floating
124
+ * modes (`AppEmbed`).
125
+ */
126
+ export function appDatalayerCreatePayload(app, instance) {
127
+ return {
128
+ // Under the application's id, over AG-UI: what the workspace's
129
+ // chat addresses (`AppRenderer` gives it as `agentId`).
130
+ name: app.id,
131
+ transport: 'ag-ui',
132
+ agent_spec_id: agentIdOf(app),
133
+ app_spec: dumpAppspec(app),
134
+ // Code Mode calls every tool through `execute_code`, which an
135
+ // application without a shell is refused: its tools are called
136
+ // one by one instead, each decided by its rules.
137
+ enable_codemode: Boolean(app.permissions?.computer?.shell),
138
+ ...(instance
139
+ ? {
140
+ app_instance: {
141
+ app_uid: instance.appUid ?? '',
142
+ deployment_uid: instance.deploymentUid ?? '',
143
+ version: instance.version ?? 0,
144
+ },
145
+ }
146
+ : {}),
147
+ };
148
+ }
69
149
  /**
70
150
  * An application in a LOOP workspace: its agent, its starters, its layout.
71
151
  *
@@ -76,8 +156,7 @@ export function defineAppPlugin(app) {
76
156
  /** The agentspec a runtime is allocated with before an application's agent is created on it. */
77
157
  export const DATALAYER_BOOTSTRAP_AGENTSPEC = 'example-simple';
78
158
  const NO_PLUGINS = [];
79
- export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, ...embed }) {
80
- const chatOnly = app.interface.layout === 'chat';
159
+ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, frame = false, ...embed }) {
81
160
  /*
82
161
  * The application plugin, made once per application. `LoopEmbed` rebuilds
83
162
  * its whole reactor when its plugins change, so a new plugin on every
@@ -88,36 +167,20 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
88
167
  const appPlugin = useMemo(() => (app.agent ? defineAppPlugin(app) : null),
89
168
  // eslint-disable-next-line react-hooks/exhaustive-deps
90
169
  [source]);
91
- const allPlugins = useMemo(() => (appPlugin ? [appPlugin, ...plugins] : plugins), [appPlugin, plugins]);
170
+ const withPage = hasAppPage(app);
171
+ const pagePlugin = useMemo(() => (withPage ? defineAppPagePlugin(app) : null),
172
+ // eslint-disable-next-line react-hooks/exhaustive-deps
173
+ [source, withPage]);
174
+ const allPlugins = useMemo(() => appPlugin
175
+ ? [appPlugin, ...(pagePlugin ? [pagePlugin] : []), ...plugins]
176
+ : plugins, [appPlugin, pagePlugin, plugins]);
92
177
  /*
93
178
  * On Datalayer, the application runs on a runtime: allocated with a plain
94
179
  * agentspec, its agent created there with the application's spec — so that
95
180
  * the runtime registers it and decides every tool call by its rules (LOOP
96
181
  * R-03, R-05), as `loop apps run --cloud` does.
97
182
  */
98
- const datalayerCreatePayload = useMemo(() => app.agent
99
- ? {
100
- // Under the application's id, over AG-UI: what the workspace's
101
- // chat addresses (`agentId` below).
102
- name: app.id,
103
- transport: 'ag-ui',
104
- agent_spec_id: agentIdOf(app),
105
- app_spec: dumpAppspec(app),
106
- // Code Mode calls every tool through `execute_code`, which an
107
- // application without a shell is refused: its tools are called
108
- // one by one instead, each decided by its rules.
109
- enable_codemode: Boolean(app.permissions?.computer?.shell),
110
- ...(instance
111
- ? {
112
- app_instance: {
113
- app_uid: instance.appUid ?? '',
114
- deployment_uid: instance.deploymentUid ?? '',
115
- version: instance.version ?? 0,
116
- },
117
- }
118
- : {}),
119
- }
120
- : undefined,
183
+ const datalayerCreatePayload = useMemo(() => (app.agent ? appDatalayerCreatePayload(app, instance) : undefined),
121
184
  // eslint-disable-next-line react-hooks/exhaustive-deps
122
185
  [source, instance?.appUid, instance?.deploymentUid, instance?.version]);
123
186
  if (!appPlugin) {
@@ -129,7 +192,11 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, .
129
192
  , {
130
193
  // The application's own agent: created under its id, so that its
131
194
  // spec is applied to an agent of its own, never to the one it extends.
132
- agentId: app.id, editors: !chatOnly, showViewSelector: !chatOnly, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
195
+ agentId: app.id, ...appLayoutOptions(app), ...(frame
196
+ ? {
197
+ frameTitle: [app.emoji, app.name].filter(Boolean).join(' '),
198
+ }
199
+ : {}), teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
133
200
  // Where an application runs is decided by its host — the Studio's
134
201
  // Preview, the hosted page — not offered to its user.
135
202
  targetFixed: true,
@@ -0,0 +1,15 @@
1
+ import type { Agentspec } from '../../types/agentspecs';
2
+ /** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
3
+ export declare function resolveAgentspec(reference: string): Agentspec | undefined;
4
+ /**
5
+ * What the agent is told: its spec's system prompt, then what the
6
+ * application tells it on top — as the runtime joins them.
7
+ */
8
+ export declare function agentInstructions(spec: Pick<Agentspec, 'systemPrompt'> | undefined, extra?: string): string | undefined;
9
+ /**
10
+ * Why an agent turned in the page cannot run, or undefined when it can.
11
+ *
12
+ * An agent named by a reference that resolves to nothing would answer with
13
+ * no instructions — bare — which is not the agent that was asked for.
14
+ */
15
+ export declare function inPageAgentRefusal(reference: string | undefined, spec: Agentspec | undefined): string | undefined;
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The agent an application runs, as the page turns it (LOOP H-01, E-11).
7
+ *
8
+ * On a runtime, an application's agent is its agent's spec — an agent of the
9
+ * catalogue, or a Cog's, which is an agent equipped with Frames, their
10
+ * context already in its system prompt — and the application's own
11
+ * instructions on top of it (`get_library_agent_spec` and the create route
12
+ * in `agent_runtimes/routes/agents.py`). The page turns the same agent in the
13
+ * browser, so it resolves it the same way: an application on a Cog that the
14
+ * page looked up among the agentspecs alone ran with no instructions at all.
15
+ *
16
+ * @module loop/apps/agent
17
+ */
18
+ import { getAgentspecs } from '../../specs/agents';
19
+ import { getCog } from '../../specs/cogs';
20
+ /** An agent of the catalogue, or a Cog's agent, by `id` or `id:version`. */
21
+ export function resolveAgentspec(reference) {
22
+ return getAgentspecs(reference) ?? getCog(reference)?.spec;
23
+ }
24
+ /**
25
+ * What the agent is told: its spec's system prompt, then what the
26
+ * application tells it on top — as the runtime joins them.
27
+ */
28
+ export function agentInstructions(spec, extra) {
29
+ const parts = [spec?.systemPrompt?.trim(), extra?.trim()].filter((part) => Boolean(part));
30
+ return parts.length > 0 ? parts.join('\n\n') : undefined;
31
+ }
32
+ /**
33
+ * Why an agent turned in the page cannot run, or undefined when it can.
34
+ *
35
+ * An agent named by a reference that resolves to nothing would answer with
36
+ * no instructions — bare — which is not the agent that was asked for.
37
+ */
38
+ export function inPageAgentRefusal(reference, spec) {
39
+ return reference && !spec
40
+ ? `There is no agent or Cog named “${reference}” in this page, so it cannot answer here.`
41
+ : undefined;
42
+ }
@@ -24,13 +24,17 @@
24
24
  *
25
25
  * @module loop/apps/appspec
26
26
  */
27
- import type { AppAccent, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
27
+ import type { AppAccent, AppAssistantCharacter, AppEmbedMode, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
28
28
  /** The version of the spec itself. */
29
29
  export declare const APP_SCHEMA = "loop.app/v1";
30
30
  /** The face of an application that has not chosen one. */
31
31
  export declare const DEFAULT_EMOJI = "\uD83D\uDC40";
32
32
  export declare const APP_KINDS: AppKind[];
33
33
  export declare const APP_ACCENTS: AppAccent[];
34
+ /** The four ways an application sits in another product's page (LOOP D-07). */
35
+ export declare const APP_EMBED_MODES: AppEmbedMode[];
36
+ /** The characters an Appspec may name for its floating assistant (LOOP T-24). */
37
+ export declare const APP_ASSISTANT_CHARACTERS: AppAssistantCharacter[];
34
38
  /** The layout a kind starts with. */
35
39
  export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
36
40
  /** What an application keeps of what it did, when it does not say. */
@@ -16,6 +16,20 @@ export const APP_ACCENTS = [
16
16
  'sun',
17
17
  'violet',
18
18
  ];
19
+ /** The four ways an application sits in another product's page (LOOP D-07). */
20
+ export const APP_EMBED_MODES = [
21
+ 'inline',
22
+ 'bubble',
23
+ 'panel',
24
+ 'assistant',
25
+ ];
26
+ /** The characters an Appspec may name for its floating assistant (LOOP T-24). */
27
+ export const APP_ASSISTANT_CHARACTERS = [
28
+ 'paperclip',
29
+ 'wizard',
30
+ 'cat',
31
+ 'eyes',
32
+ ];
19
33
  const ACTION_CLASS_NAMES = [
20
34
  'read',
21
35
  'write',
@@ -203,6 +217,9 @@ function parseInterface(data, kind) {
203
217
  if (isData(data.surface)) {
204
218
  parsed.surface = parseSurface(data.surface);
205
219
  }
220
+ if (APP_ASSISTANT_CHARACTERS.includes(data.assistant)) {
221
+ parsed.assistant = data.assistant;
222
+ }
206
223
  return parsed;
207
224
  }
208
225
  function parseCriterion(data) {
@@ -239,7 +256,7 @@ function parseDeployment(data) {
239
256
  }
240
257
  if (isData(data.embedded)) {
241
258
  deployment.embedded = {
242
- mode: oneOf(data.embedded.mode, ['inline', 'bubble', 'panel'], 'inline'),
259
+ mode: oneOf(data.embedded.mode, APP_EMBED_MODES, 'inline'),
243
260
  origins: texts(data.embedded.origins),
244
261
  };
245
262
  }
@@ -454,6 +471,9 @@ function dumpInterface(spec, kind) {
454
471
  if (spec.surface) {
455
472
  writer.data.surface = dumpSurface(spec.surface);
456
473
  }
474
+ if (spec.assistant) {
475
+ writer.data.assistant = spec.assistant;
476
+ }
457
477
  return writer.data;
458
478
  }
459
479
  function dumpDecision(decision) {
@@ -370,7 +370,8 @@ const ENUMS = {
370
370
  'feedback',
371
371
  ],
372
372
  visibility: ['private', 'invited', 'organization', 'link', 'public'],
373
- mode: ['inline', 'bubble', 'panel'],
373
+ mode: ['inline', 'bubble', 'panel', 'assistant'],
374
+ character: ['paperclip', 'wizard', 'cat', 'eyes'],
374
375
  trigger: ['schedule', 'event', 'once'],
375
376
  criterion: ['metric', 'noul', 'choice', 'score'],
376
377
  direction: ['higher', 'lower'],
@@ -498,6 +499,7 @@ export function documentShapeProblems(document) {
498
499
  mapping(d.interface, 'interface', ui => {
499
500
  oneOf(ui.layout, ENUMS.layout, 'interface.layout');
500
501
  oneOf(ui.accent, ENUMS.accent, 'interface.accent');
502
+ oneOf(ui.assistant, ENUMS.character, 'interface.assistant');
501
503
  text(ui.welcome, 'interface.welcome');
502
504
  texts(ui.components, 'interface.components');
503
505
  records(ui.starters, 'interface.starters', (item, where) => {
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * @module loop/apps
5
5
  */
6
+ export * from './agent';
6
7
  export * from './AppRenderer';
7
8
  export * from './appspec';
8
9
  export * from './checks';
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * @module loop/apps
9
9
  */
10
+ export * from './agent';
10
11
  export * from './AppRenderer';
11
12
  export * from './appspec';
12
13
  export * from './checks';
@@ -4,6 +4,8 @@ import type { ExecutionState } from '@datalayer/jupyter-react/kernel-indicator';
4
4
  import type { ToolbarItem } from '@datalayer/primer-addons';
5
5
  import type { FrontendToolDefinition } from '../../types/tools';
6
6
  import type { ContextSnapshotData } from '../../types/context';
7
+ import type { AssistantCharacter } from '../../chat/assistant/characters';
8
+ import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
7
9
  /** Lifecycle of the sandbox a workspace is attached to. */
8
10
  export type SandboxState = 'idle' | 'starting' | 'running' | 'stopping' | 'error';
9
11
  /** What the workspace knows about the sandbox behind it. */
@@ -481,6 +483,14 @@ export type AgentBlueprintContribution = {
481
483
  specId?: string;
482
484
  /** Extra fields merged into the server's create-agent payload. */
483
485
  createPayload?: Record<string, unknown>;
486
+ /**
487
+ * What the agent is told on top of its spec's system prompt — an
488
+ * application's instructions — when the page turns it, as a runtime
489
+ * appends them.
490
+ */
491
+ instructions?: string;
492
+ /** The model it runs on in the page, in place of its spec's. */
493
+ model?: string;
484
494
  };
485
495
  export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
486
496
  /** One conversation opener a plugin offers on the empty chat. */
@@ -503,6 +513,19 @@ export type ChatSuggestionContribution = {
503
513
  suggestions: ChatSuggestionItem[];
504
514
  };
505
515
  export declare const LoopChatSuggestion: import("@datalayer/reactor").ContributionPoint<ChatSuggestionContribution>;
516
+ /**
517
+ * A character the floating assistant may show (LOOP T-24): a drawing — one
518
+ * of Datalayer's own (T-25) or a plugin's — or a character read from a file
519
+ * a person holds the rights to (T-26). The catalogue of characters is what
520
+ * the enabled plugins contribute here, and nothing else; an application
521
+ * names one by id (`interface.assistant`).
522
+ */
523
+ export type AssistantCharacterContribution = {
524
+ /** Stable id, what an application names. */
525
+ id: string;
526
+ character: AssistantCharacter | AssistantCharacterData;
527
+ };
528
+ export declare const LoopAssistantCharacter: import("@datalayer/reactor").ContributionPoint<AssistantCharacterContribution>;
506
529
  /**
507
530
  * The per-example chat extras a host feeds the loop's conversation live.
508
531
  *
@@ -141,6 +141,7 @@ export const LoopChatComposer = defineContributionPoint('loop.chat.composer');
141
141
  export const LoopChatHeader = defineContributionPoint('loop.chat.header');
142
142
  export const LoopAgentBlueprint = defineContributionPoint('loop.agent.blueprint');
143
143
  export const LoopChatSuggestion = defineContributionPoint('loop.chat.suggestion');
144
+ export const LoopAssistantCharacter = defineContributionPoint('loop.assistant.character');
144
145
  export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
145
146
  export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
146
147
  export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
@@ -0,0 +1,42 @@
1
+ import type { CSSProperties, JSX, ReactNode } from 'react';
2
+ import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
3
+ import { type AppInstance } from '../apps/AppRenderer';
4
+ import { type EmbedColorMode } from './embedConfig';
5
+ export type AppEmbedProps = {
6
+ /** The application. */
7
+ app: AppSpec;
8
+ /** How it sits in the page; the Appspec's `deployment.embedded.mode` by default. */
9
+ mode?: AppEmbedMode;
10
+ /** The host's accent; the application's own by default. */
11
+ accent?: AppAccent;
12
+ /** Light, dark, or the visitor's system. */
13
+ colorMode?: EmbedColorMode;
14
+ /** The host's face, a CSS font family; the theme's by default. */
15
+ font?: string;
16
+ /**
17
+ * An agent-runtimes server the application's agent runs on. Without one,
18
+ * it runs on a Datalayer runtime, launched with the visitor's Datalayer
19
+ * credentials.
20
+ */
21
+ serverUrl?: string;
22
+ /** What its record is kept under, on Datalayer. */
23
+ instance?: AppInstance;
24
+ /** Height of an inline application. */
25
+ height?: number | string;
26
+ /**
27
+ * Draw overlays — menus, dialogs — inside the embed's own root rather than
28
+ * in Primer's, in the host's `<body>`: what the element asks for, since a
29
+ * shadow root's styles stop at its edge. A React host leaves it off.
30
+ */
31
+ ownPortal?: boolean;
32
+ };
33
+ /** The `loop` theme, with the application's (or the host's) accent and face. */
34
+ export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
35
+ accent: AppAccent;
36
+ colorMode: 'light' | 'dark';
37
+ font?: string;
38
+ children: ReactNode;
39
+ style?: CSSProperties;
40
+ }): JSX.Element;
41
+ export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, height, ownPortal, }: AppEmbedProps): JSX.Element;
42
+ export default AppEmbed;
@@ -0,0 +1,205 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * An application in another product's page, as a React component (LOOP
8
+ * D-07, D-09, D-11): what the `<datalayer-app>` element draws, and what a
9
+ * host that uses React mounts itself.
10
+ *
11
+ * Four modes, as the Appspec says (`deployment.embedded.mode`) or the host:
12
+ *
13
+ * - **inline** — the application where the component is: `AppRenderer`, its
14
+ * page beside its conversation when it has one;
15
+ * - **bubble** — a round button in the corner that opens the conversation in
16
+ * a popup, the copilot pattern;
17
+ * - **panel** — the conversation at the right edge of the page, at full
18
+ * height;
19
+ * - **assistant** — the application's character on the page, speaking in a
20
+ * balloon (§6.9), the one its Appspec names (`interface.assistant`).
21
+ *
22
+ * The three floating modes are `ChatFloating`'s, given the application's
23
+ * name, welcome, starters and accent, and its agent's endpoint — created
24
+ * with the application's spec in its payload, so the runtime enforces its
25
+ * rules there as it does inline.
26
+ *
27
+ * In the `loop` theme with the application's accent (T-12, T-05), which the
28
+ * host may override with the face and the mode (D-11).
29
+ *
30
+ * @module loop/embed/AppEmbed
31
+ */
32
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
33
+ import { registerPortalRoot } from '@primer/react';
34
+ import { DatalayerThemeProvider, loopTheme } from '@datalayer/primer-addons';
35
+ import { useIAMStore } from '@datalayer/core/lib/state/substates/IAMState';
36
+ import { ChatFloating } from '../../chat/ChatFloating';
37
+ import { useAgentRuntimes } from '../../hooks/useAgentRuntimes';
38
+ import { AppRenderer, DATALAYER_BOOTSTRAP_AGENTSPEC, appDatalayerCreatePayload, defineAppPlugin, } from '../apps/AppRenderer';
39
+ import { LoopAgentBlueprint } from '../core';
40
+ import { ensureServerAgent } from '../plugins/agents/switchable';
41
+ import { floatingViewOf } from './embedConfig';
42
+ import { embedThemeStyles } from './embedTheme';
43
+ /** What the application's agent is created with on an agent-runtimes server. */
44
+ function serverCreatePayload(app) {
45
+ const blueprint = (defineAppPlugin(app).contributes ?? []).find((item) => item.point === LoopAgentBlueprint);
46
+ return blueprint?.value.createPayload ?? { name: app.id };
47
+ }
48
+ /**
49
+ * The application's agent on the host's agent-runtimes server: created
50
+ * there with the application's spec unless it is there already, as the
51
+ * workspace's Local target does.
52
+ */
53
+ function useServerAgent(app, serverUrl) {
54
+ const [state, setState] = useState({
55
+ waiting: 'Starting…',
56
+ });
57
+ const source = JSON.stringify(app);
58
+ useEffect(() => {
59
+ let cancelled = false;
60
+ const base = serverUrl.replace(/\/+$/, '');
61
+ setState({ waiting: 'Starting…' });
62
+ ensureServerAgent(base, app.id, serverCreatePayload(app)).then(() => {
63
+ if (!cancelled) {
64
+ setState({
65
+ protocol: {
66
+ type: 'ag-ui',
67
+ endpoint: `${base}/api/v1/ag-ui/${app.id}/`,
68
+ agentId: app.id,
69
+ },
70
+ });
71
+ }
72
+ }, (error) => {
73
+ if (!cancelled) {
74
+ setState({
75
+ error: error instanceof Error ? error.message : String(error),
76
+ });
77
+ }
78
+ });
79
+ return () => {
80
+ cancelled = true;
81
+ };
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, [serverUrl, source]);
84
+ return state;
85
+ }
86
+ /**
87
+ * The application's agent on a Datalayer runtime: allocated with a plain
88
+ * agentspec, the agent created there with the application's spec — the
89
+ * runtime `AppRenderer` launches inline (R-03, R-05).
90
+ */
91
+ function useDatalayerAgent(app, instance) {
92
+ const token = useIAMStore(state => state.token);
93
+ const source = JSON.stringify(app);
94
+ const agentConfig = useMemo(() => ({
95
+ name: app.id,
96
+ protocol: 'ag-ui',
97
+ agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
98
+ createPayload: appDatalayerCreatePayload(app, instance),
99
+ }),
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ [source, instance?.appUid, instance?.deploymentUid, instance?.version]);
102
+ const { runtime, error } = useAgentRuntimes({
103
+ agentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
104
+ agentConfig,
105
+ variant: 'cloud-pydanticai',
106
+ // Only for a visitor Datalayer knows: a runtime is somebody's to pay for.
107
+ autoStart: Boolean(token),
108
+ autoCreateAgent: Boolean(token),
109
+ });
110
+ if (!token) {
111
+ return {
112
+ error: `${app.name} runs on Datalayer, which needs you signed in. An application embedded for visitors who are not needs its embed token (LOOP D-14), or an agent-runtimes server of the host's own (the "server" attribute).`,
113
+ };
114
+ }
115
+ if (error) {
116
+ return { error };
117
+ }
118
+ if (!runtime?.agentBaseUrl) {
119
+ return { waiting: 'Starting…' };
120
+ }
121
+ return {
122
+ protocol: {
123
+ type: 'ag-ui',
124
+ endpoint: `${runtime.agentBaseUrl}/api/v1/ag-ui/${app.id}/`,
125
+ agentId: app.id,
126
+ authToken: token,
127
+ },
128
+ };
129
+ }
130
+ /** The application's face, as the floating chat's button and header draw it. */
131
+ function Face({ emoji }) {
132
+ return (_jsx("span", { "aria-hidden": true, style: { fontSize: 18, lineHeight: 1 }, children: emoji }));
133
+ }
134
+ /** The bubble, the panel and the assistant: `ChatFloating` in that mode. */
135
+ function FloatingChat({ app, view, colorMode, agent, }) {
136
+ const welcome = app.interface.welcome || app.description;
137
+ return (_jsx(ChatFloating
138
+ // A new agent is a new conversation.
139
+ , { defaultViewMode: view, assistantCharacter: app.interface.assistant ?? 'paperclip', protocol: agent.protocol, authToken: agent.protocol?.authToken, title: app.name, description: welcome, brandIcon: _jsx(Face, { emoji: app.emoji }), buttonIcon: _jsx(Face, { emoji: app.emoji }), buttonTooltip: `Talk to ${app.name}`, suggestions: app.interface.starters.map(starter => ({
140
+ title: starter.label,
141
+ message: starter.message,
142
+ })), themeVariant: "loop", colorMode: colorMode, showTokenUsage: false, showSettingsButton: false, showPoweredBy: false, useStore: false, launching: !agent.protocol && !agent.error, launchingMessage: agent.waiting, disabled: Boolean(agent.error), disableReason: agent.error }, agent.protocol?.endpoint ?? 'waiting'));
143
+ }
144
+ /** Floating, its agent on the host's agent-runtimes server. */
145
+ function ServerFloating({ serverUrl, ...props }) {
146
+ return (_jsx(FloatingChat, { ...props, agent: useServerAgent(props.app, serverUrl) }));
147
+ }
148
+ /** Floating, its agent on a Datalayer runtime. */
149
+ function DatalayerFloating({ instance, ...props }) {
150
+ return (_jsx(FloatingChat, { ...props, agent: useDatalayerAgent(props.app, instance) }));
151
+ }
152
+ /**
153
+ * Overlays — menus, dialogs — drawn inside the embed's own root, under the
154
+ * theme's element, so that they wear its theme: what the element asks for,
155
+ * since Primer's own portal root is in the host's `<body>`, outside the
156
+ * shadow root and its styles.
157
+ */
158
+ function OwnPortalRoot() {
159
+ const ref = useRef(null);
160
+ useLayoutEffect(() => {
161
+ if (ref.current) {
162
+ registerPortalRoot(ref.current);
163
+ }
164
+ }, []);
165
+ return _jsx("div", { ref: ref, className: "datalayer-app-portal" });
166
+ }
167
+ /** The visitor's system mode, followed while it changes. */
168
+ function useSystemMode() {
169
+ const query = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
170
+ ? window.matchMedia('(prefers-color-scheme: dark)')
171
+ : null;
172
+ const [dark, setDark] = useState(Boolean(query?.matches));
173
+ useEffect(() => {
174
+ if (!query) {
175
+ return undefined;
176
+ }
177
+ const follow = () => setDark(query.matches);
178
+ query.addEventListener?.('change', follow);
179
+ return () => query.removeEventListener?.('change', follow);
180
+ }, [query]);
181
+ return dark ? 'dark' : 'light';
182
+ }
183
+ /** The `loop` theme, with the application's (or the host's) accent and face. */
184
+ export function EmbedThemed({ accent, colorMode, font, children, style, }) {
185
+ const themeStyles = useMemo(() => embedThemeStyles({ accent, font }), [accent, font]);
186
+ return (
187
+ /*
188
+ * Marked as themed above the provider, so that the provider counts
189
+ * itself nested and leaves the host's <body> and Primer's portal root
190
+ * alone: it would otherwise write the theme's tokens, colour and face on
191
+ * the host's page (T-13). A wrapper of no box.
192
+ */
193
+ _jsx("div", { "data-color-mode": colorMode, style: { display: 'contents' }, children: _jsx(DatalayerThemeProvider, { colorMode: colorMode, theme: loopTheme, themeStyles: themeStyles, baseStyles: style, children: children }) }));
194
+ }
195
+ export function AppEmbed({ app, mode, accent, colorMode = 'auto', font, serverUrl, instance, height = 640, ownPortal = false, }) {
196
+ const system = useSystemMode();
197
+ const resolvedMode = colorMode === 'auto' ? system : colorMode;
198
+ const shownAs = mode ?? app.deployment.embedded?.mode ?? 'inline';
199
+ const view = floatingViewOf(shownAs);
200
+ return (_jsxs(EmbedThemed, { accent: accent ?? app.interface.accent, colorMode: resolvedMode, font: font, style: view
201
+ ? // Floating: nothing in the page's flow, nothing painted behind.
202
+ { backgroundColor: 'transparent' }
203
+ : { height, display: 'flex', flexDirection: 'column' }, children: [ownPortal ? _jsx(OwnPortalRoot, {}) : null, view && serverUrl ? (_jsx(ServerFloating, { app: app, view: view, serverUrl: serverUrl, colorMode: resolvedMode })) : view ? (_jsx(DatalayerFloating, { app: app, view: view, instance: instance, colorMode: resolvedMode })) : (_jsx(AppRenderer, { app: app, target: serverUrl ? 'local' : 'datalayer', ...(serverUrl ? { serverUrl } : {}), instance: instance }))] }));
204
+ }
205
+ export default AppEmbed;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The one script a host loads (LOOP D-08): it defines `<datalayer-app>` and
7
+ * nothing else. Built on its own by `npm run build:embed`
8
+ * (`vite.embed.config.ts`) into `dist-embed/datalayer-app.js`, with the
9
+ * stylesheet the element links into its shadow root beside it
10
+ * (`dist-embed/datalayer-app.css`); served from a Datalayer origin at
11
+ * `/embed/`.
12
+ *
13
+ * @module loop/embed/datalayer-app
14
+ */
15
+ import { defineDatalayerAppElement } from './element';
16
+ defineDatalayerAppElement();