@datalayer/agent-runtimes 1.3.10 → 1.3.13

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 (107) hide show
  1. package/README.md +1 -2
  2. package/lib/AgentNode.js +3 -3
  3. package/lib/api/runtimes/environments.d.ts +22 -1
  4. package/lib/api/runtimes/environments.js +31 -0
  5. package/lib/api/runtimes/healthz.js +4 -2
  6. package/lib/api/spacer/healthz.js +4 -2
  7. package/lib/chat/ChatFloating.d.ts +8 -1
  8. package/lib/chat/ChatFloating.js +5 -2
  9. package/lib/chat/base/ChatBase.js +39 -25
  10. package/lib/chat/base/modelChoice.d.ts +62 -0
  11. package/lib/chat/base/modelChoice.js +56 -0
  12. package/lib/chat/display/ButtonGlow.d.ts +9 -0
  13. package/lib/chat/display/ButtonGlow.js +60 -0
  14. package/lib/chat/display/EmptyState.js +4 -1
  15. package/lib/chat/header/ChatHeaderBase.js +4 -56
  16. package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
  17. package/lib/chat/header/ChatViewModeToggle.js +55 -0
  18. package/lib/chat/index.d.ts +1 -0
  19. package/lib/chat/index.js +1 -0
  20. package/lib/chat/messages/ChatMessageList.js +13 -5
  21. package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
  22. package/lib/chat/messages/FormSubmissionMessage.js +49 -0
  23. package/lib/chat/messages/formSubmission.d.ts +41 -0
  24. package/lib/chat/messages/formSubmission.js +90 -0
  25. package/lib/chat/prompt/menus/ModelSelector.js +11 -4
  26. package/lib/chat/styles/streamdownStyles.js +40 -0
  27. package/lib/client/mixins/RuntimesMixin.js +1 -1
  28. package/lib/client/mixins/SpacerMixin.js +3 -3
  29. package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
  30. package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
  31. package/lib/components/a2ui/index.d.ts +1 -0
  32. package/lib/components/a2ui/index.js +1 -0
  33. package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
  34. package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
  35. package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
  36. package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
  37. package/lib/components/icons/ArtifactIcon.js +2 -1
  38. package/lib/examples/A2UiAgentExample.js +36 -223
  39. package/lib/examples/A2UiJupyterOutputExample.js +11 -1
  40. package/lib/examples/A2UiViewerExample.d.ts +9 -2
  41. package/lib/examples/A2UiViewerExample.js +33 -3
  42. package/lib/examples/DecksAgent.d.ts +1 -1
  43. package/lib/examples/DecksAgent.js +1 -1
  44. package/lib/examples/main.js +37 -35
  45. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
  46. package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
  47. package/lib/loop/core/index.d.ts +44 -4
  48. package/lib/loop/core/index.js +29 -2
  49. package/lib/loop/embed/LoopEmbed.js +7 -0
  50. package/lib/loop/index.d.ts +1 -1
  51. package/lib/loop/index.js +4 -1
  52. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
  53. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
  54. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
  55. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
  56. package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
  57. package/lib/loop/plugins/a2ui-surface/index.js +91 -0
  58. package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
  59. package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
  60. package/lib/loop/plugins/chat/ChatView.js +95 -15
  61. package/lib/loop/plugins/chat/index.d.ts +14 -0
  62. package/lib/loop/plugins/chat/index.js +1 -0
  63. package/lib/loop/plugins/document/index.js +4 -0
  64. package/lib/loop/plugins/notebook/index.js +4 -0
  65. package/lib/loop/plugins/page-layout/index.d.ts +36 -0
  66. package/lib/loop/plugins/page-layout/index.js +87 -7
  67. package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
  68. package/lib/loop/presets.d.ts +19 -0
  69. package/lib/loop/presets.js +9 -1
  70. package/lib/models/Deck.d.ts +14 -0
  71. package/lib/models/Deck.js +5 -0
  72. package/lib/models/Environment.d.ts +117 -0
  73. package/lib/models/ItemType.d.ts +1 -1
  74. package/lib/models/Runtime.d.ts +6 -0
  75. package/lib/models/SpaceItem.d.ts +2 -1
  76. package/lib/models/environments.generated.d.ts +35 -1
  77. package/lib/models/environments.generated.js +1 -1
  78. package/lib/models/index.d.ts +1 -0
  79. package/lib/models/index.js +1 -0
  80. package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
  81. package/lib/protocols/BrowserAgentAdapter.js +11 -4
  82. package/lib/runtimes/actions.d.ts +5 -0
  83. package/lib/runtimes/actions.js +19 -0
  84. package/lib/specs/agents/agents.d.ts +3 -1
  85. package/lib/specs/agents/agents.js +426 -130
  86. package/lib/specs/frontendTools.d.ts +1 -0
  87. package/lib/specs/frontendTools.js +31 -0
  88. package/lib/specs/mcpServers.js +13 -13
  89. package/lib/specs/models.js +7 -7
  90. package/lib/specs/outputs.js +9 -0
  91. package/lib/specs/skills.js +7 -7
  92. package/lib/specs/teams/teams.js +1 -1
  93. package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
  94. package/lib/types/outputs.d.ts +2 -0
  95. package/package.json +10 -4
  96. package/patches/@primer+react+37.31.0.patch +390 -0
  97. package/patches/eslint-plugin-react+7.37.5.patch +65 -0
  98. package/scripts/apply-patches.sh +15 -1
  99. package/scripts/codegen/compose.py +4 -4
  100. package/scripts/codegen/generate_agents.py +1 -2
  101. package/scripts/codegen/generate_frontend_tools.py +1 -1
  102. package/scripts/codegen/generate_mcp_servers.py +2 -2
  103. package/scripts/codegen/generate_models.py +33 -8
  104. package/scripts/codegen/generate_outputs.py +2 -0
  105. package/scripts/codegen/generate_skills.py +2 -2
  106. package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
  107. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
package/README.md CHANGED
@@ -10,8 +10,7 @@
10
10
 
11
11
  # 🤖 🚀 Agent Runtimes
12
12
 
13
- [![Github Actions Status](https://github.com/datalayer/agent-runtimes/actions/workflows/build.yml/badge.svg)](https://github.com/datalayer/agent-runtimes/actions/workflows/build.yml)
14
- [![Netlify Status](https://api.netlify.com/api/v1/badges/f7f9e08a-884f-4f76-b20d-666d5873716c/deploy-status)](https://app.netlify.com/projects/agent-runtimes/deploys)
13
+ [![Github Actions Status](https://github.com/datalayer/agent-runtimes/actions/workflows/build.yaml/badge.svg)](https://github.com/datalayer/agent-runtimes/actions/workflows/build.yaml)
15
14
  [![PyPI - Version](https://img.shields.io/pypi/v/agent-runtimes)](https://pypi.org/project/agent-runtimes)
16
15
 
17
16
  **Agent Runtimes** is a unified library for deploying, managing, and interacting with AI agents across multiple protocols and frameworks. It provides both a Python server for hosting agents and React components for seamless integration into web and desktop applications.
package/lib/AgentNode.js CHANGED
@@ -819,8 +819,8 @@ export function AgentNode() {
819
819
  const aiAgentsUrl = getConfigUrlFromDocument('aiAgentsUrl') ||
820
820
  import.meta.env?.VITE_DATALAYER_AI_AGENTS_URL ||
821
821
  'https://r1.datalayer.run';
822
- const jupyterMcpServerUrl = getConfigUrlFromDocument('jupyterMcpServerUrl') ||
823
- import.meta.env?.VITE_DATALAYER_JUPYTER_MCP_SERVER_URL ||
822
+ const datalayerMcpServerUrl = getConfigUrlFromDocument('datalayerMcpServerUrl') ||
823
+ import.meta.env?.VITE_DATALAYER_MCP_SERVER_URL ||
824
824
  'https://mcp.datalayer.run/mcp';
825
825
  // Seed all per-service URLs to match the main UI login behavior.
826
826
  const coreApi = coreStore.getState();
@@ -832,7 +832,7 @@ export function AgentNode() {
832
832
  libraryUrl: iamUrl,
833
833
  aiAgentsUrl,
834
834
  aiInferenceUrl: aiInferenceUrl,
835
- jupyterMcpServerUrl,
835
+ datalayerMcpServerUrl,
836
836
  otelUrl: iamUrl,
837
837
  growthUrl: iamUrl,
838
838
  successUrl: iamUrl,
@@ -1,5 +1,5 @@
1
1
  import type { EnvironmentData, ListEnvironmentsResponse } from '../../models/EnvironmentDTO';
2
- import type { ICreateEnvironmentBuildsRequest, ICreateEnvironmentRequest, ICreateEnvironmentVersionRequest, IDatalayerEnvironment, IEnvironmentArtifactsPage, IEnvironmentBuildLogChunk, IEnvironmentBuildLogPage, IEnvironmentBuildRecord, IEnvironmentBuildsCreated, IEnvironmentBuildsPage, IEnvironmentFork, IEnvironmentPublicationRecord, IEnvironmentRecord, IEnvironmentsError, IEnvironmentsPageQuery, IEnvironmentTrial, IEnvironmentValidationReport, IEnvironmentVersionRecord, IEnvironmentVersionsPage, IForkEnvironmentVersionRequest, IListEnvironmentsQuery, IPromoteEnvironmentVersionRequest, ITrialEnvironmentVersionRequest, IUpdateEnvironmentRequest, IUpdateEnvironmentVersionRequest, IValidateEnvironmentVersionRequest } from '../../models/Environment';
2
+ import type { ICreateEnvironmentBuildsRequest, ICreateEnvironmentRequest, ICreateEnvironmentVersionRequest, IDatalayerEnvironment, IEnvironmentArtifactsPage, IEnvironmentBuildLogChunk, IEnvironmentBuildLogPage, IEnvironmentBuildRecord, IEnvironmentBuildsCreated, IEnvironmentBuildsPage, IEnvironmentFork, IEnvironmentPublicationRecord, IEnvironmentQuotas, IEnvironmentRecord, IEnvironmentVersionSandboxes, IEnvironmentsError, IEnvironmentsPageQuery, IEnvironmentTrial, IEnvironmentValidationReport, IEnvironmentVersionRecord, IEnvironmentVersionsPage, IForkEnvironmentVersionRequest, IListEnvironmentsQuery, IPromoteEnvironmentVersionRequest, ITrialEnvironmentVersionRequest, IUpdateEnvironmentRequest, IUpdateEnvironmentVersionRequest, IValidateEnvironmentVersionRequest } from '../../models/Environment';
3
3
  /** What every environments call may carry. */
4
4
  export interface IEnvironmentsRequestOptions {
5
5
  /** Sent as `X-Correlation-Id`; a refusal answers with it. */
@@ -197,6 +197,27 @@ export declare const createEnvironmentBuilds: (token: string, versionUid: string
197
197
  * @returns Promise resolving to one page of builds
198
198
  */
199
199
  export declare const listEnvironmentBuilds: (token: string, versionUid: string, page?: IEnvironmentsPageQuery, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentBuildsPage>;
200
+ /**
201
+ * The sandboxes running a version now: yours by uid, anybody else's counted (E2-19).
202
+ * @param token - Authentication token
203
+ * @param versionUid - The version's uid
204
+ * @param options - Correlation id and abort signal
205
+ * @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
206
+ * @returns Promise resolving to the version's live sandboxes
207
+ */
208
+ export declare const getEnvironmentVersionSandboxes: (token: string, versionUid: string, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentVersionSandboxes>;
209
+ /**
210
+ * An owner's build quotas and what it holds against each (E1-13, E2-19).
211
+ * @param token - Authentication token
212
+ * @param owner - The caller's own when empty; an organization's by its uid
213
+ * @param options - Correlation id and abort signal
214
+ * @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
215
+ * @returns Promise resolving to the limits and the holdings
216
+ */
217
+ export declare const getEnvironmentQuotas: (token: string, owner?: {
218
+ ownerType?: "user" | "organization";
219
+ ownerUid?: string;
220
+ }, options?: IEnvironmentsRequestOptions, baseUrl?: string) => Promise<IEnvironmentQuotas>;
200
221
  /**
201
222
  * List a version's artifacts, one per variant and region.
202
223
  * @param token - Authentication token
@@ -293,6 +293,37 @@ export const listEnvironmentBuilds = async (token, versionUid, page = {}, option
293
293
  validateToken(token);
294
294
  return send(token, 'GET', registryUrl(baseUrl, `/environment-versions/${segment(versionUid, 'Version UID')}/builds`, { ...page }), options);
295
295
  };
296
+ /**
297
+ * The sandboxes running a version now: yours by uid, anybody else's counted (E2-19).
298
+ * @param token - Authentication token
299
+ * @param versionUid - The version's uid
300
+ * @param options - Correlation id and abort signal
301
+ * @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
302
+ * @returns Promise resolving to the version's live sandboxes
303
+ */
304
+ export const getEnvironmentVersionSandboxes = async (token, versionUid, options = {}, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
305
+ validateToken(token);
306
+ return send(token, 'GET', registryUrl(baseUrl, `/environment-versions/${segment(versionUid, 'Version UID')}/sandboxes`), options);
307
+ };
308
+ /**
309
+ * An owner's build quotas and what it holds against each (E1-13, E2-19).
310
+ * @param token - Authentication token
311
+ * @param owner - The caller's own when empty; an organization's by its uid
312
+ * @param options - Correlation id and abort signal
313
+ * @param baseUrl - Base URL for the API (defaults to production Runtimes URL)
314
+ * @returns Promise resolving to the limits and the holdings
315
+ */
316
+ export const getEnvironmentQuotas = async (token, owner = {}, options = {}, baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
317
+ validateToken(token);
318
+ const query = {};
319
+ if (owner.ownerType) {
320
+ query.ownerType = owner.ownerType;
321
+ }
322
+ if (owner.ownerUid) {
323
+ query.ownerUid = owner.ownerUid;
324
+ }
325
+ return send(token, 'GET', registryUrl(baseUrl, '/environment-quotas', query), options);
326
+ };
296
327
  /**
297
328
  * List a version's artifacts, one per variant and region.
298
329
  * @param token - Authentication token
@@ -36,9 +36,11 @@ export const ping = async (baseUrl = DEFAULT_SERVICE_URLS.RUNTIMES) => {
36
36
  // Check if it's a response error with status code information
37
37
  if (error.response) {
38
38
  const status = error.response.status;
39
- throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`);
39
+ throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`, { cause: error });
40
40
  }
41
41
  // Re-throw other errors (network errors, etc.)
42
- throw new Error(`Health check failed: ${error.message}`);
42
+ throw new Error(`Health check failed: ${error.message}`, {
43
+ cause: error,
44
+ });
43
45
  }
44
46
  };
@@ -36,9 +36,11 @@ export const ping = async (baseUrl = DEFAULT_SERVICE_URLS.SPACER) => {
36
36
  // Check if it's a response error with status code information
37
37
  if (error.response) {
38
38
  const status = error.response.status;
39
- throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`);
39
+ throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`, { cause: error });
40
40
  }
41
41
  // Re-throw other errors (network errors, etc.)
42
- throw new Error(`Health check failed: ${error.message}`);
42
+ throw new Error(`Health check failed: ${error.message}`, {
43
+ cause: error,
44
+ });
43
45
  }
44
46
  };
@@ -49,6 +49,13 @@ export interface ChatFloatingProps extends ChatCommonProps {
49
49
  buttonTooltip?: string;
50
50
  /** Brand color override. Defaults to the theme's `accent.emphasis` token. */
51
51
  brandColor?: string;
52
+ /**
53
+ * A soft light breathing around the button while the chat is closed, as
54
+ * if it were alive (see `ButtonGlow`). Still, and dimmer, for a reader who
55
+ * asks the system for reduced motion.
56
+ * @default true
57
+ */
58
+ buttonGlow?: boolean;
52
59
  /** Offset from edge (in pixels) */
53
60
  offset?: number;
54
61
  /** Animation duration (in ms) */
@@ -92,5 +99,5 @@ export interface ChatFloatingProps extends ChatCommonProps {
92
99
  * ChatFloating component
93
100
  * A floating chat window built on ChatBase
94
101
  */
95
- export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, }: ChatFloatingProps): React.JSX.Element;
102
+ export declare function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode, title, description, position, defaultOpen, width, height, showHeader, showButton, showNewChatButton, showClearButton, showSettingsButton, enableKeyboardShortcuts, toggleShortcut, showPoweredBy, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose, escapeToClose, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip, brandColor, buttonGlow, offset, animationDuration, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick, hideMessagesAfterToolUI, defaultViewMode, onViewModeChange, sidebarMountPoint, showPanelBackdrop, availableModels, showModelSelector, showToolsMenu, showSkillsMenu, showTokenUsage, showContextRing, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching, launchingMessage, }: ChatFloatingProps): React.JSX.Element;
96
103
  export default ChatFloating;
@@ -23,6 +23,7 @@ import { XIcon, CommentDiscussionIcon, GrabberIcon, } from '@primer/octicons-rea
23
23
  import { AiAgentIcon } from '@datalayer/icons-react';
24
24
  import { createPortal } from 'react-dom';
25
25
  import { ChatBase } from './base/ChatBase';
26
+ import { ButtonGlow } from './display/ButtonGlow';
26
27
  import { useViewportDrag } from './useViewportDrag';
27
28
  import { disabledChatViewModes, resolveMountPoint } from './viewModes';
28
29
  import { useChatOpen, useChatMessages, useChatStore, } from '../stores/chatStore';
@@ -46,7 +47,7 @@ function useIsMobile(breakpoint = 640) {
46
47
  * ChatFloating component
47
48
  * A floating chat window built on ChatBase
48
49
  */
49
- export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, }) {
50
+ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useStoreMode = true, title = 'Chat', description = 'Start a conversation with the AI agent.', position = 'bottom-right', defaultOpen = false, width = 400, height = 550, showHeader = true, showButton = true, showNewChatButton = true, showClearButton = true, showSettingsButton = false, enableKeyboardShortcuts = true, toggleShortcut = '/', showPoweredBy = true, promptVariant, mentionableAgents, poweredByProps, clickOutsideToClose = false, escapeToClose = true, className, onSettingsClick, onNewChat, onOpen, onClose, onStateUpdate, children, brandIcon, buttonIcon, buttonTooltip = 'Chat with AI', brandColor, buttonGlow = true, offset = 20, animationDuration = 200, renderToolResult, frontendTools, initialState: _initialState, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, defaultViewMode = 'floating', onViewModeChange, sidebarMountPoint = null, showPanelBackdrop = false, availableModels, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, showTokenUsage = true, showContextRing = false, themeVariant, colorMode, runtimeId, historyEndpoint, authToken, historyAuthToken, pendingPrompt, showInformation = false, onInformationClick, onToolCallStart, onToolCallComplete, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, panelProps, launching = false, launchingMessage, }) {
50
51
  // Store-based state
51
52
  const storeIsOpen = useChatOpen();
52
53
  const storeMessages = useChatMessages();
@@ -555,7 +556,9 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
555
556
  }, children: _jsxs(Box, { sx: {
556
557
  position: 'relative',
557
558
  display: 'inline-flex',
558
- }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx(Tooltip, { text: `${buttonTooltip}${shortcutHint ? ` (${shortcutHint})` : ''}`, direction: position.includes('right') ? 'w' : 'e', children: _jsx(IconButton, { icon: buttonIcon
559
+ // The glow's halos go behind the button, not behind the page.
560
+ isolation: 'isolate',
561
+ }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [buttonGlow && _jsx(ButtonGlow, { color: brandColor }), _jsx(Tooltip, { text: `${buttonTooltip}${shortcutHint ? ` (${shortcutHint})` : ''}`, direction: position.includes('right') ? 'w' : 'e', children: _jsx(IconButton, { icon: buttonIcon
559
562
  ? buttonIcon
560
563
  : CommentDiscussionIcon, "aria-label": buttonTooltip, onClick: handleToggle, size: "large", sx: {
561
564
  width: 56,
@@ -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 } from '../../specs/models';
34
+ import { AI_MODEL_CATALOGUE, DEFAULT_MODEL } 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';
@@ -48,6 +48,7 @@ import { InputPrompt } from '../prompt/InputPrompt';
48
48
  import { isFloatingChatViewMode } from '../viewModes';
49
49
  import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
50
50
  import { EphemeralNotebook } from '../notebook/EphemeralNotebook';
51
+ import { initialModelId, isOffered, usableModels } from './modelChoice';
51
52
  // EphemeralDocument statically imports `@datalayer/jupyter-lexical` (which
52
53
  // initialises Lumino-backed nodes on load). Lazy-load it so notebook-only chats
53
54
  // never pull lexical into the bundle or trigger its module side effects.
@@ -1378,9 +1379,10 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1378
1379
  ? (protocol?.options).model
1379
1380
  : undefined;
1380
1381
  const offeredModels = useMemo(() => {
1382
+ // Only what can actually be called — see `modelChoice` for the rule.
1381
1383
  const fromConfig = availableModels || configQuery.data?.models;
1382
1384
  if (fromConfig?.length) {
1383
- return fromConfig;
1385
+ return usableModels(fromConfig);
1384
1386
  }
1385
1387
  /*
1386
1388
  * The catalogue, filtered to what is worth offering.
@@ -1402,7 +1404,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1402
1404
  provider: model.provider,
1403
1405
  }));
1404
1406
  if (catalogued.length > 0) {
1405
- return catalogued;
1407
+ return usableModels(catalogued);
1406
1408
  }
1407
1409
  return browserModel
1408
1410
  ? [{ id: browserModel, name: browserModel, provider: 'inference' }]
@@ -1721,7 +1723,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1721
1723
  const timer = setTimeout(adjustTextareaHeight, 0);
1722
1724
  return () => clearTimeout(timer);
1723
1725
  }, [adjustTextareaHeight]);
1724
- // ---- Initialize model and tools when config is available ----
1726
+ // ---- The model, whenever what is offered changes ----
1725
1727
  useEffect(() => {
1726
1728
  /*
1727
1729
  * `offeredModels`, not the two sources it is built from.
@@ -1731,28 +1733,40 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1731
1733
  * sources, so that model was offered by the menu and never selected. The
1732
1734
  * menu draws nothing without a selection, so a browser agent had a model
1733
1735
  * list of one and no model control at all.
1736
+ *
1737
+ * Re-picked, not picked once. Before the server has answered, the offer
1738
+ * is the browser's catalogue, and the chat chose from it — the first row,
1739
+ * Alibaba, which the catalogue marks available because the platform is
1740
+ * entitled to it. The server then answered with the one model this
1741
+ * process can call, the guess was not in it, and nothing looked again:
1742
+ * the trigger read "none selected" while every turn was still sent to
1743
+ * Alibaba and came back asking for its key. A selection stands only while
1744
+ * it is offered and usable; otherwise the preferred model — the agent's
1745
+ * own, from the server — or the first usable one takes its place.
1734
1746
  */
1735
- if (offeredModels.length > 0 && !selectedModel) {
1736
- const modelsList = offeredModels;
1737
- const preferredModel = initialModel || configQuery.data?.defaultModel;
1738
- if (preferredModel) {
1739
- const modelExists = modelsList.some(m => m.id === preferredModel);
1740
- if (modelExists) {
1741
- setSelectedModel(preferredModel);
1742
- }
1743
- else {
1744
- const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
1745
- const firstModel = firstAvailableModel || modelsList[0];
1746
- if (firstModel)
1747
- setSelectedModel(firstModel.id);
1748
- }
1749
- }
1750
- else {
1751
- const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
1752
- const firstModel = firstAvailableModel || modelsList[0];
1753
- if (firstModel)
1754
- setSelectedModel(firstModel.id);
1755
- }
1747
+ if (offeredModels.length === 0 || isOffered(offeredModels, selectedModel)) {
1748
+ return;
1749
+ }
1750
+ // The host's word first, then the server's default, then the platform's:
1751
+ // the first row of a catalogue is an order of listing, not a choice, and
1752
+ // opening on it sent the first message to Alibaba while the footer —
1753
+ // drawn from the host's own model — said Claude.
1754
+ const opening = initialModelId(offeredModels, initialModel || configQuery.data?.defaultModel || DEFAULT_MODEL);
1755
+ if (opening && opening !== selectedModel) {
1756
+ setSelectedModel(opening);
1757
+ }
1758
+ }, [
1759
+ offeredModels,
1760
+ selectedModel,
1761
+ initialModel,
1762
+ configQuery.data?.defaultModel,
1763
+ ]);
1764
+ // ---- Initialize tools when config is available ----
1765
+ const toolsInitializedRef = useRef(false);
1766
+ useEffect(() => {
1767
+ // Once: a later change to the offer must not undo what the reader toggled.
1768
+ if (offeredModels.length > 0 && !toolsInitializedRef.current) {
1769
+ toolsInitializedRef.current = true;
1756
1770
  const allToolIds = configQuery.data?.builtinTools?.map(tool => tool.id) || [];
1757
1771
  setEnabledTools(allToolIds);
1758
1772
  if (configQuery.data?.mcpServers) {
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Which models a chat offers, and which one it opens on.
3
+ *
4
+ * Two rules, kept out of `ChatBase` so they can be read and tested without
5
+ * rendering a chat:
6
+ *
7
+ * - **Only what can be called is offered.** A server answers with its whole
8
+ * catalogue and a flag per model, and the menu used to draw all of it, most
9
+ * rows greyed out with a reason. Thirty models to choose two from reads as
10
+ * a broken menu, and the one thing it guarantees is that a reader's first
11
+ * click lands on something that cannot answer.
12
+ * - **The default has to be one of them.** A default naming a model this
13
+ * deployment cannot use — a spec claiming the default while marked
14
+ * unavailable — opened every chat on it, and the first message came back
15
+ * as the provider's complaint about a missing key.
16
+ *
17
+ * @module chat/base/modelChoice
18
+ */
19
+ import type { ModelConfig } from '../../types/chat';
20
+ /** Whether a model can be picked: the flag says no, or says nothing. */
21
+ export declare const isUsable: (model: ModelConfig) => boolean;
22
+ /**
23
+ * The models to offer: the usable ones.
24
+ *
25
+ * When none is usable the list is kept whole. An empty menu says nothing,
26
+ * while the rows and their reasons say which key is missing.
27
+ */
28
+ export declare function usableModels(models: ModelConfig[]): ModelConfig[];
29
+ /** Whether `id` is on offer and usable — the selection may stand. */
30
+ export declare const isOffered: (offered: ModelConfig[], id?: string) => boolean;
31
+ /**
32
+ * The model a chat opens on.
33
+ *
34
+ * The preferred one when it is offered *and* usable; otherwise the first
35
+ * usable one; otherwise the first there is. `undefined` when there is none.
36
+ */
37
+ export declare function initialModelId(offered: ModelConfig[], preferred?: string | null): string | undefined;
38
+ /**
39
+ * One row of `/api/v1/configure/models`, as the server writes it.
40
+ *
41
+ * Snake case and its own flag names: the route is read by more than this
42
+ * chat, and its shape is the server's to keep.
43
+ */
44
+ export type ServerCatalogueModel = {
45
+ id: string;
46
+ name?: string;
47
+ available?: boolean;
48
+ missing_env_vars?: string[];
49
+ reason?: string;
50
+ warning?: string | null;
51
+ };
52
+ /**
53
+ * The server's catalogue in the chat's shape.
54
+ *
55
+ * The route says `available`; the chat, the menu and the rules above read
56
+ * `isAvailable`. Handed over unmapped, every row looked usable, the menu
57
+ * offered all thirty-one, and the first message of a chat that opened before
58
+ * its spec was known went to the first row — Alibaba, and its missing key.
59
+ */
60
+ export declare function readServerCatalogue(payload: {
61
+ models?: ServerCatalogueModel[];
62
+ } | null | undefined): ModelConfig[];
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /** Whether a model can be picked: the flag says no, or says nothing. */
6
+ export const isUsable = (model) => model.isAvailable !== false;
7
+ /**
8
+ * The models to offer: the usable ones.
9
+ *
10
+ * When none is usable the list is kept whole. An empty menu says nothing,
11
+ * while the rows and their reasons say which key is missing.
12
+ */
13
+ export function usableModels(models) {
14
+ const usable = models.filter(isUsable);
15
+ return usable.length > 0 ? usable : models;
16
+ }
17
+ /** Whether `id` is on offer and usable — the selection may stand. */
18
+ export const isOffered = (offered, id) => Boolean(id) && offered.some(m => m.id === id && isUsable(m));
19
+ /**
20
+ * The model a chat opens on.
21
+ *
22
+ * The preferred one when it is offered *and* usable; otherwise the first
23
+ * usable one; otherwise the first there is. `undefined` when there is none.
24
+ */
25
+ export function initialModelId(offered, preferred) {
26
+ if (preferred && offered.some(m => m.id === preferred && isUsable(m))) {
27
+ return preferred;
28
+ }
29
+ const first = offered.find(isUsable) ?? offered[0];
30
+ return first?.id;
31
+ }
32
+ /**
33
+ * The server's catalogue in the chat's shape.
34
+ *
35
+ * The route says `available`; the chat, the menu and the rules above read
36
+ * `isAvailable`. Handed over unmapped, every row looked usable, the menu
37
+ * offered all thirty-one, and the first message of a chat that opened before
38
+ * its spec was known went to the first row — Alibaba, and its missing key.
39
+ */
40
+ export function readServerCatalogue(payload) {
41
+ return (payload?.models ?? []).map(model => {
42
+ const missing = model.missing_env_vars ?? [];
43
+ const reason = model.reason ??
44
+ (missing.length > 0 ? `Set ${missing.join(', ')}` : undefined) ??
45
+ model.warning ??
46
+ undefined;
47
+ return {
48
+ id: model.id,
49
+ name: model.name ?? model.id,
50
+ isAvailable: model.available !== false,
51
+ ...(reason && model.available === false
52
+ ? { unavailableReason: reason }
53
+ : {}),
54
+ };
55
+ });
56
+ }
@@ -0,0 +1,9 @@
1
+ export interface ButtonGlowProps {
2
+ /**
3
+ * The colour of the light: the button's own. A theme token or any CSS
4
+ * colour; the theme's `accent.emphasis` when absent.
5
+ */
6
+ color?: string;
7
+ }
8
+ export declare function ButtonGlow({ color }: ButtonGlowProps): import("react").JSX.Element;
9
+ export default ButtonGlow;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, 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
+ * A soft light breathing around a round button, as if it were alive.
8
+ *
9
+ * Two blurred halos in the button's colour swell and fade on cycles of
10
+ * different lengths, 3.4 s and 5.3 s, so the light drifts rather than
11
+ * blinks: the two never line up the same way twice in a row. For a reader
12
+ * who asks the system for less motion, the halos hold still, a little dimmer.
13
+ *
14
+ * The halos sit behind the button: the parent must be `position: relative`
15
+ * and make a stacking context of its own (`isolation: 'isolate'`), or they
16
+ * would slip behind the page instead.
17
+ *
18
+ * @module chat/display/ButtonGlow
19
+ */
20
+ import { Box } from '@datalayer/primer-addons';
21
+ const halo = (color) => ({
22
+ position: 'absolute',
23
+ inset: 0,
24
+ borderRadius: '50%',
25
+ bg: color || 'accent.emphasis',
26
+ zIndex: -1,
27
+ pointerEvents: 'none',
28
+ willChange: 'transform, opacity',
29
+ });
30
+ export function ButtonGlow({ color }) {
31
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { "aria-hidden": true, "data-button-glow": "breath", sx: {
32
+ ...halo(color),
33
+ filter: 'blur(10px)',
34
+ animation: 'agentRuntimesGlowBreath 3.4s ease-in-out infinite',
35
+ '@keyframes agentRuntimesGlowBreath': {
36
+ '0%, 100%': { transform: 'scale(0.9)', opacity: 0.35 },
37
+ '50%': { transform: 'scale(1.25)', opacity: 0.7 },
38
+ },
39
+ '@media (prefers-reduced-motion: reduce)': {
40
+ animation: 'none',
41
+ transform: 'scale(1.1)',
42
+ opacity: 0.4,
43
+ },
44
+ } }), _jsx(Box, { "aria-hidden": true, "data-button-glow": "aura", sx: {
45
+ ...halo(color),
46
+ filter: 'blur(18px)',
47
+ animation: 'agentRuntimesGlowAura 5.3s ease-in-out infinite',
48
+ '@keyframes agentRuntimesGlowAura': {
49
+ '0%, 100%': { transform: 'scale(1)', opacity: 0.15 },
50
+ '40%': { transform: 'scale(1.55)', opacity: 0.4 },
51
+ '70%': { transform: 'scale(1.3)', opacity: 0.25 },
52
+ },
53
+ '@media (prefers-reduced-motion: reduce)': {
54
+ animation: 'none',
55
+ transform: 'scale(1.3)',
56
+ opacity: 0.2,
57
+ },
58
+ } })] }));
59
+ }
60
+ export default ButtonGlow;
@@ -50,6 +50,9 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
50
50
  cursor: 'pointer',
51
51
  display: 'inline-flex',
52
52
  alignItems: 'center',
53
+ // A short opener sits in the middle of its fixed-width chip, not
54
+ // against its left edge with the rest of the chip empty.
55
+ justifyContent: 'center',
53
56
  width: SUGGESTION_CHIP_WIDTH,
54
57
  flex: `0 0 ${SUGGESTION_CHIP_WIDTH}px`,
55
58
  '&:hover': {
@@ -57,7 +60,7 @@ export function ChatEmptyState({ emptyState, brandIcon, description, suggestions
57
60
  color: 'var(--button-primary-fgColor-rest)',
58
61
  borderColor: 'accent.emphasis',
59
62
  },
60
- }, onClick: () => handleSuggestionClick(suggestion), children: _jsx(Truncate, { title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
63
+ }, onClick: () => handleSuggestionClick(suggestion), children: _jsx(Truncate, { inline: true, title: suggestion.message, maxWidth: "100%", sx: { minWidth: 0 }, children: suggestion.title }) }, index))) }));
61
64
  return (_jsx(ThemeProvider, { children: _jsxs(Box, { sx: {
62
65
  display: 'flex',
63
66
  flexDirection: 'column',
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Heading, IconButton, Label, Text, Tooltip, Truncate, } from '@primer/react';
2
+ import { Heading, IconButton, Label, Text, Truncate } from '@primer/react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
4
  import { KernelIndicator } from '@datalayer/jupyter-react';
5
- import { PlusIcon, TrashIcon, GearIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, GrabberIcon, InfoIcon, } from '@primer/octicons-react';
6
- import { CHAT_VIEW_MODES, SIDEBAR_NEEDS_MOUNT_POINT } from '../viewModes';
5
+ import { PlusIcon, TrashIcon, GearIcon, InfoIcon, } from '@primer/octicons-react';
6
+ import { ChatViewModeToggle } from './ChatViewModeToggle';
7
7
  import { AiAgentIcon } from '@datalayer/icons-react';
8
8
  import { EphemeralSurfaceControl } from '../EphemeralSurfaceControl';
9
9
  export function toRuntimeExecutionState(runtimeStatus) {
@@ -29,16 +29,6 @@ export function toRuntimeExecutionState(runtimeStatus) {
29
29
  return undefined;
30
30
  }
31
31
  // ---------------------------------------------------------------------------
32
- // Props
33
- // ---------------------------------------------------------------------------
34
- /** The drawing of each mode in the header's toggle. */
35
- const VIEW_MODE_ICONS = {
36
- floating: CommentDiscussionIcon,
37
- 'floating-small': DeviceMobileIcon,
38
- 'floating-draggable': GrabberIcon,
39
- sidebar: SidebarExpandIcon,
40
- };
41
- // ---------------------------------------------------------------------------
42
32
  // Component
43
33
  // ---------------------------------------------------------------------------
44
34
  export function ChatBaseHeader({ title, subtitle, disableReason, brandIcon, headerContent, headerActions, showInformation, onInformationClick, padding, kernelIndicatorState, kernelIndicatorPlacement = 'left', runtimeStatus, kernel, kernelEnvironmentName, kernelCpu, kernelMemory, kernelGpu, headerButtons, messageCount, onNewChat, onClear, chatViewMode, onChatViewModeChange, disabledViewModes = [], showEphemeralSurfaceControl = false, enableEphemeralNotebookOption = false, enableEphemeralDocumentOption = false, ephemeralSurfaceMode = 'none', onEphemeralSurfaceModeChange, }) {
@@ -90,47 +80,5 @@ export function ChatBaseHeader({ title, subtitle, disableReason, brandIcon, head
90
80
  color: 'fg.muted',
91
81
  minWidth: 0,
92
82
  maxWidth: '100%',
93
- }, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), disableReason && (_jsx(Label, { variant: "attention", sx: { flexShrink: 0 }, children: disableReason })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick })), kernelIndicatorPlacement === 'center' && kernelIndicatorElement] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [kernelIndicatorPlacement === 'right' && kernelIndicatorElement, headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), showEphemeralSurfaceControl && (_jsx(EphemeralSurfaceControl, { mode: ephemeralSurfaceMode, onChange: onEphemeralSurfaceModeChange, enableNotebook: enableEphemeralNotebookOption, enableDocument: enableEphemeralDocumentOption })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
94
- display: 'inline-flex',
95
- alignItems: 'center',
96
- bg: 'neutral.muted',
97
- borderRadius: '6px',
98
- p: '2px',
99
- gap: '1px',
100
- }, children: CHAT_VIEW_MODES.map(({ mode, label }) => {
101
- const ModeIcon = VIEW_MODE_ICONS[mode];
102
- const selected = chatViewMode === mode;
103
- /* Greyed rather than left out: the person sees the mode
104
- exists, and that this page has no place for it. */
105
- const disabled = disabledViewModes.includes(mode);
106
- return (_jsx(Tooltip, { text: disabled && mode === 'sidebar'
107
- ? `${label} — ${SIDEBAR_NEEDS_MOUNT_POINT}`
108
- : label, direction: "n", children: _jsx(Box, { as: "button", "aria-label": label, "aria-disabled": disabled || undefined, onClick: () => {
109
- if (!disabled) {
110
- onChatViewModeChange(mode);
111
- }
112
- }, sx: {
113
- display: 'inline-flex',
114
- alignItems: 'center',
115
- justifyContent: 'center',
116
- width: 26,
117
- height: 24,
118
- borderRadius: '4px',
119
- border: 'none',
120
- cursor: disabled ? 'not-allowed' : 'pointer',
121
- opacity: disabled ? 0.45 : 1,
122
- bg: selected ? 'canvas.default' : 'transparent',
123
- boxShadow: selected ? 'shadow.small' : 'none',
124
- color: selected ? 'fg.default' : 'fg.muted',
125
- transition: 'all 0.15s ease',
126
- '&:hover': disabled
127
- ? {}
128
- : {
129
- color: 'fg.default',
130
- bg: selected
131
- ? 'canvas.default'
132
- : 'neutral.subtle',
133
- },
134
- }, children: _jsx(ModeIcon, { size: 14 }) }) }, mode));
135
- }) })), headerActions] })] }) }));
83
+ }, children: _jsx(Truncate, { title: subtitle, maxWidth: "40ch", children: subtitle }) }))] })), disableReason && (_jsx(Label, { variant: "attention", sx: { flexShrink: 0 }, children: disableReason })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick })), kernelIndicatorPlacement === 'center' && kernelIndicatorElement] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, flexShrink: 0 }, children: [kernelIndicatorPlacement === 'right' && kernelIndicatorElement, headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: onNewChat })), headerButtons?.showClear && messageCount > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: onClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), showEphemeralSurfaceControl && (_jsx(EphemeralSurfaceControl, { mode: ephemeralSurfaceMode, onChange: onEphemeralSurfaceModeChange, enableNotebook: enableEphemeralNotebookOption, enableDocument: enableEphemeralDocumentOption })), chatViewMode && onChatViewModeChange && (_jsx(ChatViewModeToggle, { value: chatViewMode, onChange: onChatViewModeChange, disabledModes: disabledViewModes })), headerActions] })] }) }));
136
84
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The segmented toggle for how a chat is shown — the four modes of
3
+ * `CHAT_VIEW_MODES`, drawn as icons in a pill, the current one raised.
4
+ *
5
+ * It grew up in the chat header, and lives apart from it so the same
6
+ * control can stand elsewhere: a composer floating over a page as a
7
+ * draggable card carries it in its own footer, where the person deciding
8
+ * how the chat should sit has the chat under their hands. The rules — the
9
+ * order, the words, which modes are greyed out — stay in `viewModes.ts`;
10
+ * this is only their drawing.
11
+ *
12
+ * @module chat/header/ChatViewModeToggle
13
+ */
14
+ import type { JSX } from 'react';
15
+ import { type Icon } from '@primer/octicons-react';
16
+ import type { ChatViewMode } from '../../types/chat';
17
+ /** The drawing of each mode. */
18
+ export declare const VIEW_MODE_ICONS: Record<ChatViewMode, Icon>;
19
+ export type ChatViewModeToggleProps = {
20
+ /** The mode the chat is in now. */
21
+ value?: ChatViewMode;
22
+ /** The mode the person picked. */
23
+ onChange: (mode: ChatViewMode) => void;
24
+ /** Modes shown greyed out: the sidebar, when the host has no mount point. */
25
+ disabledModes?: readonly ChatViewMode[];
26
+ /** Where the tooltips open. Above by default; below for a control at the top of its window. */
27
+ tooltipDirection?: 'n' | 's';
28
+ };
29
+ export declare function ChatViewModeToggle({ value, onChange, disabledModes, tooltipDirection, }: ChatViewModeToggleProps): JSX.Element;