@datalayer/agent-runtimes 1.3.15 → 1.3.16

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 (45) hide show
  1. package/README.md +1 -1
  2. package/lib/chat/base/ChatBase.js +3 -2
  3. package/lib/config/AgentConfiguration.d.ts +1 -1
  4. package/lib/loop/embed/LoopEmbed.d.ts +6 -1
  5. package/lib/loop/embed/LoopEmbed.js +3 -2
  6. package/lib/loop/plugins/chat/ChatView.js +9 -4
  7. package/lib/loop/plugins/chat/index.d.ts +10 -0
  8. package/lib/loop/plugins/chat/index.js +1 -0
  9. package/lib/loop/presets.d.ts +6 -0
  10. package/lib/loop/presets.js +2 -1
  11. package/lib/loop/shell/LoopWorkspace.d.ts +14 -0
  12. package/lib/loop/shell/LoopWorkspace.js +20 -6
  13. package/lib/loop/shell/index.d.ts +1 -1
  14. package/lib/loop/shell/index.js +1 -1
  15. package/lib/loop/shell/useWorkspaceFullScreen.d.ts +17 -4
  16. package/lib/loop/shell/useWorkspaceFullScreen.js +23 -6
  17. package/lib/specs/agents/agents.d.ts +1 -0
  18. package/lib/specs/agents/agents.js +338 -284
  19. package/lib/specs/events.d.ts +6 -1
  20. package/lib/specs/events.js +144 -1
  21. package/lib/specs/index.d.ts +2 -0
  22. package/lib/specs/index.js +2 -0
  23. package/lib/specs/mcpServers.js +2 -2
  24. package/lib/specs/memory.js +4 -0
  25. package/lib/specs/modelProviders.d.ts +22 -0
  26. package/lib/specs/modelProviders.js +102 -0
  27. package/lib/specs/models.d.ts +31 -0
  28. package/lib/specs/models.js +215 -0
  29. package/lib/specs/outputs.js +2 -2
  30. package/lib/specs/skills.js +1 -1
  31. package/lib/specs/teams/teams.js +18 -18
  32. package/lib/specs/uiPlugins.d.ts +16 -0
  33. package/lib/specs/uiPlugins.js +40 -0
  34. package/lib/types/agentspecs.d.ts +18 -2
  35. package/lib/types/memory.d.ts +2 -0
  36. package/lib/types/models.d.ts +52 -0
  37. package/package.json +1 -1
  38. package/scripts/check-cloudflare-models.py +122 -0
  39. package/scripts/codegen/generate_agents.py +6 -4
  40. package/scripts/codegen/generate_memory.py +2 -0
  41. package/scripts/codegen/generate_model_providers.py +246 -0
  42. package/scripts/codegen/generate_models.py +135 -4
  43. package/scripts/codegen/generate_sandbox_agents.py +1 -1
  44. package/scripts/codegen/generate_ui_plugins.py +226 -0
  45. package/scripts/gen_workers.py +2 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  ~ BSD 3-Clause License
5
5
  -->
6
6
 
7
- [![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)
7
+ [![Datalayer](https://images.datalayer.io/legacy/datalayer-25.svg)](https://datalayer.ai)
8
8
 
9
9
  [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)
10
10
 
@@ -31,7 +31,7 @@ import { useChatStore } from '../../stores/chatStore';
31
31
  import { useConversationStore } from '../../stores/conversationStore';
32
32
  import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/messages';
33
33
  import { AgentDetails } from '../../agents/AgentDetails';
34
- import { AI_MODEL_CATALOGUE, DEFAULT_MODEL } from '../../specs/models';
34
+ import { AI_MODEL_CATALOGUE, DEFAULT_MODEL, isChatModel, } from '../../specs/models';
35
35
  import { internalQueryClient, isToolCallMessage, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
36
36
  import { useConfig, useSkills, useSkillActions, useContextSnapshot, useSandbox, } from '../../hooks';
37
37
  import { useAgentRuntimeWebSocket } from '../../hooks/useAgentRuntimes';
@@ -1397,7 +1397,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1397
1397
  * four are current.
1398
1398
  */
1399
1399
  const catalogued = Object.values(AI_MODEL_CATALOGUE)
1400
- .filter(model => model.available)
1400
+ // A typed-judgment model answers typed questions, not a chat.
1401
+ .filter(model => model.available && isChatModel(model))
1401
1402
  .map(model => ({
1402
1403
  id: model.id,
1403
1404
  name: model.name,
@@ -28,7 +28,7 @@ export interface LibraryAgentspec {
28
28
  welcomeDocument?: string | null;
29
29
  sandboxVariant?: string | null;
30
30
  protocol?: string | null;
31
- uiExtension?: string | null;
31
+ uiPlugin?: string | null;
32
32
  trigger?: Record<string, unknown> | null;
33
33
  modelConfig?: Record<string, unknown> | null;
34
34
  mcpServerTools?: Array<Record<string, unknown>> | null;
@@ -29,6 +29,11 @@ export type LoopEmbedProps = LoopPresetOptions & {
29
29
  chatHeaderActions?: ReactNode;
30
30
  /** The host's own controls for the workspace header row. */
31
31
  headerActions?: ReactNode;
32
+ /**
33
+ * An element in the host's own bar to render the header's controls in,
34
+ * instead of a header row. See `LoopWorkspaceProps.headerContainer`.
35
+ */
36
+ headerContainer?: HTMLElement | null;
32
37
  /**
33
38
  * Draw the workspace inside a window frame with this title bar.
34
39
  *
@@ -53,5 +58,5 @@ export type LoopEmbedProps = LoopPresetOptions & {
53
58
  */
54
59
  plugins?: PluginRef[];
55
60
  };
56
- export declare function LoopEmbed({ agentId, showHeader, chatHeaderActions, headerActions, frameTitle, frameHeight, plugins, ...preset }: LoopEmbedProps): React.JSX.Element;
61
+ export declare function LoopEmbed({ agentId, showHeader, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, plugins, ...preset }: LoopEmbedProps): React.JSX.Element;
57
62
  export default LoopEmbed;
@@ -30,7 +30,7 @@ import { internalQueryClient } from '../../utils';
30
30
  import { buildLoopReactor, LoopWorkspace } from '../shell';
31
31
  import { loopPlugins } from '../presets';
32
32
  import { WindowFrame } from '../plugins/window-frame';
33
- export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions, headerActions, frameTitle, frameHeight, plugins = [], ...preset }) {
33
+ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions, headerActions, headerContainer, frameTitle, frameHeight, plugins = [], ...preset }) {
34
34
  /*
35
35
  * The latest `firstPromptHook`, kept in a ref rather than closed over
36
36
  * directly.
@@ -69,6 +69,7 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
69
69
  preset.chatHeaderButtons,
70
70
  preset.promptPlacement,
71
71
  preset.autoFocusPrompt,
72
+ preset.suggestionLabels,
72
73
  preset.fullScreenTopOffset,
73
74
  preset.showAgentVariants,
74
75
  preset.agentSummary,
@@ -119,7 +120,7 @@ export function LoopEmbed({ agentId = '', showHeader = false, chatHeaderActions,
119
120
  * view and the workspace draws "No view is available yet".
120
121
  */
121
122
  useReactor(reactor);
122
- const shell = (_jsx(LoopWorkspace, { serverUrl: preset.serverUrl ?? '', agentId: agentId, reactor: reactor, manageReactor: false, showViewSelector: preset.showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions }));
123
+ const shell = (_jsx(LoopWorkspace, { serverUrl: preset.serverUrl ?? '', agentId: agentId, reactor: reactor, manageReactor: false, showViewSelector: preset.showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer }));
123
124
  return (_jsx(QueryClientProvider, { client: internalQueryClient, children: frameTitle !== undefined ? (_jsx(WindowFrame, { title: frameTitle, height: frameHeight, children: shell })) : (_jsx(Box, { sx: { height: '100%', minHeight: 0 }, children: shell })) }));
124
125
  }
125
126
  export default LoopEmbed;
@@ -37,7 +37,7 @@ import { agentRuntimeStore, useAgentRuntimeContextSnapshot, } from '../../../sto
37
37
  import { useIAMStore } from '../../../state';
38
38
  import { useConfig } from '../../../hooks/useConfig';
39
39
  import { useSkills, useSkillActions } from '../../../hooks/useSkills';
40
- import { AI_MODEL_CATALOGUE } from '../../../specs/models';
40
+ import { AI_MODEL_CATALOGUE, isChatModel } from '../../../specs/models';
41
41
  import { LoopAgentBlueprint, LoopAgentGate, LoopChatComposer, LoopChatExtras, LoopChatHeader, LoopChatLayout, LoopChatSuggestion, LoopChatSurface, LoopChatTurn, LoopFrontendTool, LoopSlots, canOpenView, onPromptFocusRequest, onSurfaceRequest, useLoopPromptStore, } from '../../core';
42
42
  import { turnWritersOf } from './turnState';
43
43
  import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
@@ -1153,7 +1153,8 @@ export default function ChatView({ workspace }) {
1153
1153
  const offeredModels = useMemo(() => catalogModels.length > 0
1154
1154
  ? catalogModels
1155
1155
  : Object.values(AI_MODEL_CATALOGUE)
1156
- .filter(model => model.available)
1156
+ // A typed-judgment model answers typed questions, not a chat.
1157
+ .filter(model => model.available && isChatModel(model))
1157
1158
  .map(model => ({
1158
1159
  id: model.id,
1159
1160
  name: model.name,
@@ -1475,7 +1476,9 @@ export default function ChatView({ workspace }) {
1475
1476
  // With the prompt on top the openers are already chips under
1476
1477
  // it; the empty state repeating them was the same three buttons
1477
1478
  // twice on one screen.
1478
- suggestions: topPrompt || layout ? [] : chatSuggestions,
1479
+ suggestions: topPrompt || layout || config?.suggestionLabels === false
1480
+ ? []
1481
+ : chatSuggestions,
1479
1482
  /*
1480
1483
  The title bar arrives as a plugin: the chat-header plugin
1481
1484
  contributes the component, this view hands it the assembled
@@ -1598,7 +1601,9 @@ export default function ChatView({ workspace }) {
1598
1601
  // through the extras channel — the A2UI examples draw their
1599
1602
  // surface here. Wins over the notebook surfaces in ChatBase.
1600
1603
  renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
1601
- const chipsEl = chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": "Suggested prompts", sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
1604
+ // Off when the host asked for the menu alone: the composer's suggestions
1605
+ // control carries the same openers (`suggestions` above).
1606
+ const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": "Suggested prompts", sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
1602
1607
  // The whole request, where the chip shows only its summary.
1603
1608
  title: item.message, disabled: busy || chatDisabled, onClick: () => void handleSend(item.message), sx: {
1604
1609
  appearance: 'none',
@@ -105,6 +105,16 @@ export type ChatPluginConfig = {
105
105
  * them. Such a host passes `false` and lets the reader arrive at the prompt.
106
106
  */
107
107
  autoFocusPrompt: boolean;
108
+ /**
109
+ * Whether the openers are drawn as a row of labels by the composer.
110
+ *
111
+ * On by default: a person who has just opened the workspace sees what it is
112
+ * worth asking without opening anything. The same openers are always behind
113
+ * the composer's own suggestions menu, so a host with little room — a frame
114
+ * on a landing page, where the labels would be a row of the first screen —
115
+ * turns the labels off and loses nothing.
116
+ */
117
+ suggestionLabels: boolean;
108
118
  /**
109
119
  * Pixels to reserve at the top of the workspace when it goes full screen —
110
120
  * room for a host's own fixed header, drawn outside the workspace
@@ -48,6 +48,7 @@ export const ChatPlugin = definePlugin({
48
48
  hidePrompt: false,
49
49
  promptPlacement: 'bottom',
50
50
  autoFocusPrompt: true,
51
+ suggestionLabels: true,
51
52
  fullScreenTopOffset: 0,
52
53
  },
53
54
  displayName: 'Chat',
@@ -54,6 +54,12 @@ export type LoopPresetOptions = {
54
54
  * does not scroll the reader into the middle of the page.
55
55
  */
56
56
  autoFocusPrompt?: boolean;
57
+ /**
58
+ * Whether the openers show as labels by the composer. True by default; the
59
+ * composer's suggestions menu offers them either way. See
60
+ * `ChatPluginConfig.suggestionLabels`.
61
+ */
62
+ suggestionLabels?: boolean;
57
63
  /**
58
64
  * Pixels to reserve at the top of the workspace when it goes full screen,
59
65
  * for a host's own fixed header. Passed through to the chat plugin — see
@@ -52,7 +52,7 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
52
52
  * be installed or signed into, and the switches a demonstration wants are off.
53
53
  */
54
54
  export function loopPlugins(options = {}) {
55
- const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
55
+ const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
56
56
  return [
57
57
  // The chat owns the editor beside it, so which one opens is its
58
58
  // configuration rather than the workspace's.
@@ -66,6 +66,7 @@ export function loopPlugins(options = {}) {
66
66
  headerButtons: { newChat: chatHeaderButtons, clear: chatHeaderButtons },
67
67
  promptPlacement: floatingPrompt ? 'floating' : promptPlacement,
68
68
  autoFocusPrompt,
69
+ suggestionLabels,
69
70
  fullScreenTopOffset,
70
71
  }),
71
72
  // The composer and the title bar are plugins of their own: the chat
@@ -103,6 +103,20 @@ export type LoopWorkspaceProps = {
103
103
  * through `LoopSlots.header`.
104
104
  */
105
105
  headerActions?: ReactNode;
106
+ /**
107
+ * Where the header's controls go, when not in a row of their own.
108
+ *
109
+ * A host that already draws a bar above the workspace — a window's title
110
+ * bar, a frame with its own tabs — hands over an element in it, and the
111
+ * controls the header would have carried (the host's `headerActions`, every
112
+ * plugin's `LoopSlots.header` contribution, the view switcher) are rendered
113
+ * there instead, and the workspace draws no header row. They stay inside
114
+ * the workspace's React tree, so they keep their context and their state.
115
+ *
116
+ * `null` while the host's element is not mounted yet: nothing is rendered,
117
+ * and no row is drawn in its place.
118
+ */
119
+ headerContainer?: HTMLElement | null;
106
120
  };
107
121
  /** Build the platform for a set of plugins. */
108
122
  export declare function buildLoopReactor(extensions: PluginRef[]): ReactorPlatform;
@@ -1,5 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
3
4
  import { Box } from '@primer/react';
4
5
  import { buildReactorFromPlugins, onView, } from '@datalayer/reactor';
5
6
  import { ReactorSlot, ReactorViewHost, useContributions, useReactorEvent, useSlotComponents, useReactor, } from '@datalayer/reactor/react';
@@ -11,21 +12,21 @@ export function buildLoopReactor(extensions) {
11
12
  return buildReactorFromPlugins(extensions);
12
13
  }
13
14
  export function LoopWorkspace(props) {
14
- const { serverUrl, agentId, conversationId, model, sandbox = { state: 'idle' }, extensions = [], reactor: providedReactor, manageReactor = true, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, } = props;
15
+ const { serverUrl, agentId, conversationId, model, sandbox = { state: 'idle' }, extensions = [], reactor: providedReactor, manageReactor = true, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, } = props;
15
16
  // Building the platform is a one-time act: rebuilding it on every render
16
17
  // would restart every plugin.
17
18
  const reactor = useMemo(() => providedReactor ?? buildLoopReactor(extensions),
18
19
  // eslint-disable-next-line react-hooks/exhaustive-deps
19
20
  [providedReactor]);
20
21
  useReactor(reactor, { autoStart: manageReactor });
21
- return (_jsx(WorkspaceBody, { serverUrl: serverUrl, agentId: agentId, conversationId: conversationId, model: model, initialSandbox: sandbox, initialViewType: initialViewType, onSend: onSend, layout: layout, showViewSelector: showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions }));
22
+ return (_jsx(WorkspaceBody, { serverUrl: serverUrl, agentId: agentId, conversationId: conversationId, model: model, initialSandbox: sandbox, initialViewType: initialViewType, onSend: onSend, layout: layout, showViewSelector: showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer }));
22
23
  }
23
24
  /**
24
25
  * Split from `LoopWorkspace` so the hooks below run with the platform already
25
26
  * registered — `useContributions` needs a reactor in the store, and `useReactor`
26
27
  * registers it during the parent's render.
27
28
  */
28
- function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, model, initialSandbox, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, }) {
29
+ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, model, initialSandbox, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, }) {
29
30
  /*
30
31
  * Which agent the session is talking to.
31
32
  *
@@ -146,6 +147,12 @@ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, mod
146
147
  // activates whatever was waiting for it. A plugin that declares neither is
147
148
  // untouched, so this costs nothing until somebody uses it.
148
149
  useReactorEvent(effectiveViewType ? onView(effectiveViewType) : undefined);
150
+ /*
151
+ * The header's controls: the host's first, then the plugins', then the
152
+ * view switcher on the trailing edge. Drawn in the header row, or in the
153
+ * host's own bar when it handed one over (`headerContainer`).
154
+ */
155
+ const headerControls = (_jsxs(_Fragment, { children: [headerActions, _jsx(ReactorSlot, { slot: LoopSlots.header, props: { workspace } }), showViewSelector ? (_jsx(ViewSwitcher, { views: views, workspace: workspace, compact: compact })) : null] }));
149
156
  return (_jsxs(Box
150
157
  /*
151
158
  Marked so a plugin inside can find the workspace it is part of.
@@ -169,7 +176,14 @@ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, mod
169
176
  position: 'relative',
170
177
  bg: 'canvas.default',
171
178
  color: 'fg.default',
172
- }, children: [showHeader ? (_jsxs(Box, { as: "header", sx: {
179
+ }, children: [showHeader && headerContainer
180
+ ? createPortal(_jsx(Box, { sx: {
181
+ display: 'flex',
182
+ alignItems: 'center',
183
+ flexWrap: 'wrap',
184
+ gap: 2,
185
+ }, children: headerControls }), headerContainer)
186
+ : null, showHeader && headerContainer === undefined ? (_jsx(Box, { as: "header", sx: {
173
187
  display: 'flex',
174
188
  alignItems: 'center',
175
189
  // In a column there is no room for a row: the header wraps rather
@@ -191,7 +205,7 @@ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, mod
191
205
  borderBottom: '1px solid',
192
206
  borderColor: 'border.default',
193
207
  flex: '0 0 auto',
194
- }, children: [headerActions, _jsx(ReactorSlot, { slot: LoopSlots.header, props: { workspace } }), showViewSelector ? (_jsx(ViewSwitcher, { views: views, workspace: workspace, compact: compact })) : null] })) : null, _jsxs(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: [_jsx(Box, { sx: {
208
+ }, children: headerControls })) : null, _jsxs(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: [_jsx(Box, { sx: {
195
209
  flex: '1 1 auto',
196
210
  minWidth: 0,
197
211
  minHeight: 0,
@@ -6,5 +6,5 @@
6
6
  export { LoopWorkspace, buildLoopReactor, type LoopWorkspaceProps, } from './LoopWorkspace';
7
7
  export { ViewSwitcher, type ViewSwitcherProps } from './ViewSwitcher';
8
8
  export { PluginToggles, type PluginTogglesProps } from './PluginToggles';
9
- export { useWorkspaceFullScreen, type WorkspaceFullScreen, } from './useWorkspaceFullScreen';
9
+ export { fullScreenNode, useWorkspaceFullScreen, type WorkspaceFullScreen, } from './useWorkspaceFullScreen';
10
10
  export { WorkspaceFullScreenAction } from './WorkspaceFullScreenAction';
@@ -10,5 +10,5 @@
10
10
  export { LoopWorkspace, buildLoopReactor, } from './LoopWorkspace';
11
11
  export { ViewSwitcher } from './ViewSwitcher';
12
12
  export { PluginToggles } from './PluginToggles';
13
- export { useWorkspaceFullScreen, } from './useWorkspaceFullScreen';
13
+ export { fullScreenNode, useWorkspaceFullScreen, } from './useWorkspaceFullScreen';
14
14
  export { WorkspaceFullScreenAction } from './WorkspaceFullScreenAction';
@@ -79,15 +79,28 @@ export type WorkspaceFullScreenOptions = {
79
79
  */
80
80
  topOffset?: number | string;
81
81
  };
82
+ /**
83
+ * What full screen promotes, found from the control that asked.
84
+ *
85
+ * The workspace (`[data-loop-workspace]`) — unless a host marked something
86
+ * around it with `data-loop-fullscreen-root`. A host that draws its own bar
87
+ * above the workspace and has the header's controls rendered in it
88
+ * (`LoopWorkspace`'s `headerContainer`) marks the element holding both: the
89
+ * controls are then outside the workspace, and promoting the workspace alone
90
+ * would leave them — the one that leaves full screen included — under the
91
+ * overlay. The anchor itself when there is neither.
92
+ */
93
+ export declare function fullScreenNode(anchor: HTMLElement | null): HTMLElement | null;
82
94
  /**
83
95
  * Full-screen state and the toggle, anchored by any element inside the
84
96
  * workspace.
85
97
  *
86
98
  * The element promoted is the whole workspace — the nearest
87
- * `[data-loop-workspace]` above the anchor — so the header's controls come
88
- * along instead of being left on the page underneath. With no workspace
89
- * around it, the anchor's own element is promoted, which is what a view
90
- * mounted without a shell would want.
99
+ * `[data-loop-workspace]` above the anchor, or the host's
100
+ * `[data-loop-fullscreen-root]` around it (see `fullScreenNode`) — so the
101
+ * header's controls come along instead of being left on the page underneath.
102
+ * With neither around it, the anchor's own element is promoted, which is what
103
+ * a view mounted without a shell would want.
91
104
  */
92
105
  export declare function useWorkspaceFullScreen(anchorRef: RefObject<HTMLElement | null>, options?: WorkspaceFullScreenOptions): WorkspaceFullScreen;
93
106
  export default useWorkspaceFullScreen;
@@ -21,15 +21,32 @@ export function resolveTopOffset(offset) {
21
21
  ? Math.max(0, Math.round(element.getBoundingClientRect().bottom))
22
22
  : 0;
23
23
  }
24
+ /**
25
+ * What full screen promotes, found from the control that asked.
26
+ *
27
+ * The workspace (`[data-loop-workspace]`) — unless a host marked something
28
+ * around it with `data-loop-fullscreen-root`. A host that draws its own bar
29
+ * above the workspace and has the header's controls rendered in it
30
+ * (`LoopWorkspace`'s `headerContainer`) marks the element holding both: the
31
+ * controls are then outside the workspace, and promoting the workspace alone
32
+ * would leave them — the one that leaves full screen included — under the
33
+ * overlay. The anchor itself when there is neither.
34
+ */
35
+ export function fullScreenNode(anchor) {
36
+ return (anchor?.closest('[data-loop-fullscreen-root]') ??
37
+ anchor?.closest('[data-loop-workspace]') ??
38
+ anchor);
39
+ }
24
40
  /**
25
41
  * Full-screen state and the toggle, anchored by any element inside the
26
42
  * workspace.
27
43
  *
28
44
  * The element promoted is the whole workspace — the nearest
29
- * `[data-loop-workspace]` above the anchor — so the header's controls come
30
- * along instead of being left on the page underneath. With no workspace
31
- * around it, the anchor's own element is promoted, which is what a view
32
- * mounted without a shell would want.
45
+ * `[data-loop-workspace]` above the anchor, or the host's
46
+ * `[data-loop-fullscreen-root]` around it (see `fullScreenNode`) — so the
47
+ * header's controls come along instead of being left on the page underneath.
48
+ * With neither around it, the anchor's own element is promoted, which is what
49
+ * a view mounted without a shell would want.
33
50
  */
34
51
  export function useWorkspaceFullScreen(anchorRef, options = {}) {
35
52
  const { forceOverlay = false, paintOverlay = false, topOffset = 0 } = options;
@@ -66,7 +83,7 @@ export function useWorkspaceFullScreen(anchorRef, options = {}) {
66
83
  * runs — so at full screen a reader would lose the two controls most
67
84
  * worth having room for.
68
85
  */
69
- const node = anchorRef.current?.closest('[data-loop-workspace]') ?? anchorRef.current;
86
+ const node = fullScreenNode(anchorRef.current);
70
87
  if (fullScreen) {
71
88
  if (usingApi.current && document.fullscreenElement) {
72
89
  void document.exitFullscreen();
@@ -116,7 +133,7 @@ export function useWorkspaceFullScreen(anchorRef, options = {}) {
116
133
  if (!paintOverlay || !fullScreen || usingApi.current) {
117
134
  return undefined;
118
135
  }
119
- const node = anchorRef.current?.closest('[data-loop-workspace]') ?? anchorRef.current;
136
+ const node = fullScreenNode(anchorRef.current);
120
137
  if (!node) {
121
138
  return undefined;
122
139
  }
@@ -22,6 +22,7 @@ export declare const EXAMPLE_ANALYZE_EXCEL_SPREADSHEET_AGENTSPEC_0_0_1: Agentspe
22
22
  export declare const EXAMPLE_BACKEND_TOOL_RENDERING_AGENTSPEC_0_0_1: Agentspec;
23
23
  export declare const EXAMPLE_BUILD_NOTEBOOK_WITH_ONE_PROMPT_AGENTSPEC_0_0_1: Agentspec;
24
24
  export declare const EXAMPLE_CHECKPOINTS_AGENTSPEC_0_0_1: Agentspec;
25
+ export declare const EXAMPLE_CLOUDFLARE_AGENTSPEC_0_0_1: Agentspec;
25
26
  export declare const EXAMPLE_CODE_SANDBOX_AGENTSPEC_0_0_1: Agentspec;
26
27
  export declare const EXAMPLE_CODEMODE_AGENTSPEC_0_0_1: Agentspec;
27
28
  export declare const EXAMPLE_COMPACTION_AGENTSPEC_0_0_1: Agentspec;