@datalayer/agent-runtimes 0.0.11 → 0.0.12

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 (136) hide show
  1. package/README.md +2 -2
  2. package/lib/Agent.d.ts +29 -0
  3. package/lib/Agent.js +131 -0
  4. package/lib/AgentLexical.d.ts +34 -0
  5. package/lib/AgentLexical.js +296 -0
  6. package/lib/AgentNotebook.d.ts +19 -0
  7. package/lib/AgentNotebook.js +192 -0
  8. package/lib/agent-lexical-main.d.ts +1 -0
  9. package/lib/agent-lexical-main.js +11 -0
  10. package/lib/agent-main.d.ts +1 -0
  11. package/lib/agent-main.js +11 -0
  12. package/lib/agent-notebook-main.d.ts +1 -0
  13. package/lib/agent-notebook-main.js +12 -0
  14. package/lib/components/AgentConfiguration.d.ts +4 -22
  15. package/lib/components/AgentConfiguration.js +8 -8
  16. package/lib/components/chat/components/AgentDetails.d.ts +3 -1
  17. package/lib/components/chat/components/AgentDetails.js +6 -6
  18. package/lib/components/chat/components/Chat.d.ts +29 -3
  19. package/lib/components/chat/components/Chat.js +64 -59
  20. package/lib/components/chat/components/ChatFloating.d.ts +34 -12
  21. package/lib/components/chat/components/ChatFloating.js +54 -21
  22. package/lib/components/chat/components/ChatInline.d.ts +5 -1
  23. package/lib/components/chat/components/ChatInline.js +8 -1
  24. package/lib/components/chat/components/ChatSidebar.d.ts +6 -1
  25. package/lib/components/chat/components/ChatSidebar.js +2 -2
  26. package/lib/components/chat/components/ChatStandalone.d.ts +6 -1
  27. package/lib/components/chat/components/ChatStandalone.js +2 -2
  28. package/lib/components/chat/components/base/ChatBase.d.ts +49 -8
  29. package/lib/components/chat/components/base/ChatBase.js +544 -149
  30. package/lib/components/chat/components/base/InputPrompt.d.ts +42 -0
  31. package/lib/components/chat/components/base/InputPrompt.js +131 -0
  32. package/lib/components/chat/components/index.d.ts +3 -3
  33. package/lib/components/chat/components/index.js +1 -1
  34. package/lib/components/chat/components/parts/ReasoningPart.js +2 -4
  35. package/lib/components/chat/components/parts/TextPart.js +2 -70
  36. package/lib/components/chat/components/styles/streamdownStyles.d.ts +23 -0
  37. package/lib/components/chat/components/styles/streamdownStyles.js +319 -0
  38. package/lib/components/chat/index.d.ts +1 -1
  39. package/lib/components/chat/index.js +1 -1
  40. package/lib/components/chat/inference/DatalayerInferenceProvider.js +16 -12
  41. package/lib/components/chat/inference/SelfHostedInferenceProvider.js +16 -12
  42. package/lib/components/chat/protocols/AGUIAdapter.d.ts +10 -3
  43. package/lib/components/chat/protocols/AGUIAdapter.js +123 -44
  44. package/lib/components/chat/types/tool.d.ts +5 -2
  45. package/lib/components/index.d.ts +1 -18
  46. package/lib/components/index.js +0 -9
  47. package/lib/config/index.d.ts +0 -4
  48. package/lib/config/index.js +0 -4
  49. package/lib/examples/A2UiRestaurantExample.js +1 -1
  50. package/lib/examples/AgentRuntimeChatExample.d.ts +15 -0
  51. package/lib/examples/AgentRuntimeChatExample.js +126 -0
  52. package/lib/examples/{AgentSpaceFormExample.d.ts → AgentRuntimeFormExample.d.ts} +3 -3
  53. package/lib/examples/{AgentSpaceFormExample.js → AgentRuntimeFormExample.js} +10 -8
  54. package/lib/examples/AgentRuntimeLexicalExample.js +6 -3
  55. package/lib/examples/AgentRuntimeLexicalSidebarExample.js +8 -1
  56. package/lib/examples/AgentRuntimeNotebookExample.js +6 -5
  57. package/lib/examples/CopilotKitNotebookExample.js +2 -2
  58. package/lib/examples/JupyterNotebookExample.js +2 -2
  59. package/lib/{components → examples/components}/Header.d.ts +2 -1
  60. package/lib/{components → examples/components}/HeaderControls.js +1 -1
  61. package/lib/{components → examples/components}/LexicalEditor.d.ts +6 -1
  62. package/lib/{components → examples/components}/LexicalEditor.js +4 -4
  63. package/lib/{components → examples/components}/MainContent.d.ts +1 -1
  64. package/lib/{components → examples/components}/MainContent.js +7 -5
  65. package/lib/examples/components/index.d.ts +16 -0
  66. package/lib/examples/components/index.js +13 -0
  67. package/lib/examples/example-selector.js +2 -1
  68. package/lib/examples/index.d.ts +1 -1
  69. package/lib/examples/index.js +1 -1
  70. package/lib/examples/main.js +2 -2
  71. package/lib/examples/stores/examplesStore.d.ts +2 -23
  72. package/lib/index.d.ts +2 -1
  73. package/lib/index.js +1 -0
  74. package/lib/lexical/ChatInlinePlugin.d.ts +13 -2
  75. package/lib/lexical/ChatInlinePlugin.js +41 -179
  76. package/lib/lexical/index.d.ts +1 -0
  77. package/lib/lexical/index.js +1 -0
  78. package/lib/lexical/useChatInlineToolbarItems.d.ts +28 -0
  79. package/lib/lexical/useChatInlineToolbarItems.js +163 -0
  80. package/lib/runtime/useAgentRuntime.d.ts +1 -1
  81. package/lib/runtime/useAgentRuntime.js +1 -1
  82. package/lib/{config/agents/code-ai → specs/agents/codeai}/agents.d.ts +5 -2
  83. package/lib/specs/agents/codeai/agents.js +151 -0
  84. package/lib/{config → specs}/agents/codemode-paper/agents.d.ts +4 -2
  85. package/lib/{config → specs}/agents/codemode-paper/agents.js +39 -19
  86. package/lib/{config → specs}/agents/datalayer-ai/agents.d.ts +4 -2
  87. package/lib/{config → specs}/agents/datalayer-ai/agents.js +17 -2
  88. package/lib/{config → specs}/agents/index.d.ts +3 -1
  89. package/lib/{config → specs}/agents/index.js +12 -3
  90. package/lib/{config → specs}/envvars.d.ts +1 -0
  91. package/lib/{config → specs}/envvars.js +10 -0
  92. package/lib/specs/index.d.ts +5 -0
  93. package/lib/specs/index.js +9 -0
  94. package/lib/{config → specs}/mcpServers.d.ts +2 -1
  95. package/lib/{config → specs}/mcpServers.js +23 -1
  96. package/lib/specs/models.d.ts +68 -0
  97. package/lib/specs/models.js +239 -0
  98. package/lib/state/substates/AIAgentState.d.ts +0 -1
  99. package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.d.ts +11 -22
  100. package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.js +5 -5
  101. package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +6 -6
  102. package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +4 -4
  103. package/lib/tools/adapters/agent-runtimes/notebookHooks.d.ts +6 -6
  104. package/lib/tools/adapters/agent-runtimes/notebookHooks.js +4 -4
  105. package/lib/{types.d.ts → types/Types.d.ts} +32 -6
  106. package/lib/types/index.d.ts +1 -0
  107. package/lib/types/index.js +1 -0
  108. package/package.json +11 -5
  109. package/scripts/codegen/generate_agents.py +53 -13
  110. package/scripts/codegen/generate_envvars.py +1 -1
  111. package/scripts/codegen/generate_mcp_servers.py +5 -5
  112. package/scripts/codegen/generate_models.py +486 -0
  113. package/scripts/codegen/generate_skills.py +2 -2
  114. package/style/primer-primitives.css +22 -0
  115. package/lib/components/chat/components/elements/ChatInputPrompt.d.ts +0 -37
  116. package/lib/components/chat/components/elements/ChatInputPrompt.js +0 -150
  117. package/lib/config/agents/code-ai/agents.js +0 -70
  118. /package/lib/{components → examples/components}/FooterMetrics.d.ts +0 -0
  119. /package/lib/{components → examples/components}/FooterMetrics.js +0 -0
  120. /package/lib/{components → examples/components}/Header.js +0 -0
  121. /package/lib/{components → examples/components}/HeaderControls.d.ts +0 -0
  122. /package/lib/{components → examples/components}/MockFileBrowser.d.ts +0 -0
  123. /package/lib/{components → examples/components}/MockFileBrowser.js +0 -0
  124. /package/lib/{components → examples/components}/SessionTabs.d.ts +0 -0
  125. /package/lib/{components → examples/components}/SessionTabs.js +0 -0
  126. /package/lib/{components → examples/components}/TimeTravel.d.ts +0 -0
  127. /package/lib/{components → examples/components}/TimeTravel.js +0 -0
  128. /package/lib/{config/agents/code-ai → specs/agents/codeai}/index.d.ts +0 -0
  129. /package/lib/{config/agents/code-ai → specs/agents/codeai}/index.js +0 -0
  130. /package/lib/{config → specs}/agents/codemode-paper/index.d.ts +0 -0
  131. /package/lib/{config → specs}/agents/codemode-paper/index.js +0 -0
  132. /package/lib/{config → specs}/agents/datalayer-ai/index.d.ts +0 -0
  133. /package/lib/{config → specs}/agents/datalayer-ai/index.js +0 -0
  134. /package/lib/{config → specs}/skills.d.ts +0 -0
  135. /package/lib/{config → specs}/skills.js +0 -0
  136. /package/lib/{types.js → types/Types.js} +0 -0
@@ -17,12 +17,14 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
17
17
  */
18
18
  import { useContext } from 'react';
19
19
  import { useCallback, useEffect, useRef, useState, } from 'react';
20
- import { Heading, Text, Spinner, IconButton, Textarea, Button, ActionMenu, ActionList, LabelGroup, Label, ToggleSwitch, } from '@primer/react';
20
+ import { Heading, Text, Spinner, IconButton, Button, ActionMenu, ActionList, LabelGroup, Label, ToggleSwitch, } from '@primer/react';
21
21
  import { Box } from '@datalayer/primer-addons';
22
- import { AlertIcon, PlusIcon, TrashIcon, GearIcon, PersonIcon, PaperAirplaneIcon, SquareCircleIcon, ToolsIcon, AiModelIcon, BriefcaseIcon, } from '@primer/octicons-react';
22
+ import { AlertIcon, PlusIcon, TrashIcon, GearIcon, PersonIcon, ToolsIcon, AiModelIcon, BriefcaseIcon, CircleIcon, SquareFillIcon, CommentDiscussionIcon, DeviceMobileIcon, SidebarExpandIcon, InfoIcon, } from '@primer/octicons-react';
23
23
  import { AiAgentIcon } from '@datalayer/icons-react';
24
+ import ReactECharts from 'echarts-for-react';
24
25
  import { useQuery, QueryClient, QueryClientProvider, QueryClientContext, } from '@tanstack/react-query';
25
26
  import { Streamdown } from 'streamdown';
27
+ import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/streamdownStyles';
26
28
  import { PoweredByTag } from '../elements/PoweredByTag';
27
29
  import { requestAPI } from '../../handler';
28
30
  import { useChatStore } from '../../store/chatStore';
@@ -30,6 +32,7 @@ import { useConversationStore } from '../../store/conversationStore';
30
32
  import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/message';
31
33
  import { AGUIAdapter, A2AAdapter, VercelAIAdapter, ACPAdapter, } from '../../protocols';
32
34
  import { ToolCallDisplay } from '../display/ToolCallDisplay';
35
+ import { InputPrompt } from './InputPrompt';
33
36
  // Singleton QueryClient for ChatBase instances without external QueryClientProvider
34
37
  const internalQueryClient = new QueryClient({
35
38
  defaultOptions: {
@@ -94,6 +97,79 @@ function getMessageText(message) {
94
97
  .map(part => part.text)
95
98
  .join('');
96
99
  }
100
+ /**
101
+ * Convert history messages to display items.
102
+ *
103
+ * History returns:
104
+ * - Assistant messages with `toolCalls` array (tool invocations)
105
+ * - Tool messages (role='tool') with content (tool results)
106
+ *
107
+ * For display, we need to:
108
+ * 1. Keep user/assistant text messages as ChatMessage
109
+ * 2. Convert each toolCall from assistant messages into a ToolCallMessage
110
+ * 3. Match tool result messages (role='tool') to their ToolCallMessage and update result
111
+ * 4. Filter out raw tool messages (role='tool') from display — they're merged into ToolCallMessage
112
+ */
113
+ function convertHistoryToDisplayItems(messages) {
114
+ const displayItems = [];
115
+ const toolCallMap = new Map();
116
+ // First pass: collect all tool calls and build the initial display list
117
+ for (const msg of messages) {
118
+ if (msg.role === 'tool') {
119
+ // Tool result messages — will be merged later
120
+ const toolCallId = msg.metadata?.toolCallId;
121
+ if (toolCallId && toolCallMap.has(toolCallId)) {
122
+ // Update the existing tool call with the result
123
+ const toolCall = toolCallMap.get(toolCallId);
124
+ toolCall.result = msg.content;
125
+ toolCall.status = 'complete';
126
+ }
127
+ // Don't add tool messages to display — they're represented by ToolCallMessage
128
+ continue;
129
+ }
130
+ if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
131
+ // Assistant message with tool calls
132
+ // First add any text content as a regular message
133
+ const textContent = typeof msg.content === 'string' ? msg.content.trim() : '';
134
+ if (textContent) {
135
+ displayItems.push({
136
+ ...msg,
137
+ toolCalls: undefined, // Remove toolCalls from the text message
138
+ });
139
+ }
140
+ // Then add each tool call as a ToolCallMessage
141
+ // Map from message.ts ToolCallStatus to ChatBase ToolCallStatus
142
+ for (const tc of msg.toolCalls) {
143
+ let status = 'complete';
144
+ if (tc.status === 'pending' || tc.status === 'awaiting-approval') {
145
+ status = 'inProgress';
146
+ }
147
+ else if (tc.status === 'executing') {
148
+ status = 'executing';
149
+ }
150
+ else if (tc.status === 'failed') {
151
+ status = 'error';
152
+ }
153
+ const toolCallMsg = {
154
+ id: `tc-${tc.toolCallId}`,
155
+ type: 'tool-call',
156
+ toolCallId: tc.toolCallId,
157
+ toolName: tc.toolName,
158
+ args: tc.args || {},
159
+ status,
160
+ result: tc.result,
161
+ };
162
+ toolCallMap.set(tc.toolCallId, toolCallMsg);
163
+ displayItems.push(toolCallMsg);
164
+ }
165
+ }
166
+ else {
167
+ // Regular user/assistant/system message
168
+ displayItems.push(msg);
169
+ }
170
+ }
171
+ return displayItems;
172
+ }
97
173
  /**
98
174
  * Create protocol adapter based on configuration
99
175
  */
@@ -259,19 +335,63 @@ function useContextSnapshotQuery(enabled, configEndpoint, agentId, authToken) {
259
335
  });
260
336
  return result;
261
337
  }
338
+ /**
339
+ * Hook to poll sandbox execution status from the backend.
340
+ * Returns whether a sandbox is available and if code is currently executing.
341
+ */
342
+ function useSandboxStatusQuery(enabled, configEndpoint, authToken) {
343
+ const queryClient = useContext(QueryClientContext);
344
+ if (!queryClient) {
345
+ return {
346
+ data: undefined,
347
+ isLoading: false,
348
+ isError: false,
349
+ error: null,
350
+ refetch: () => Promise.resolve({}),
351
+ };
352
+ }
353
+ const statusUrl = configEndpoint
354
+ ? `${getApiBaseFromConfig(configEndpoint)}/configure/sandbox-status`
355
+ : undefined;
356
+ // eslint-disable-next-line react-hooks/rules-of-hooks
357
+ const result = useQuery({
358
+ queryKey: ['sandbox-status', statusUrl],
359
+ queryFn: async () => {
360
+ if (!statusUrl) {
361
+ throw new Error('No sandbox status URL available');
362
+ }
363
+ const headers = { 'Content-Type': 'application/json' };
364
+ if (authToken) {
365
+ headers['Authorization'] = `Bearer ${authToken}`;
366
+ }
367
+ const response = await fetch(statusUrl, { headers });
368
+ if (!response.ok) {
369
+ throw new Error(`Sandbox status fetch failed: ${response.statusText}`);
370
+ }
371
+ return response.json();
372
+ },
373
+ enabled: enabled && !!statusUrl,
374
+ refetchInterval: query => (query.state.status === 'error' ? false : 2_000),
375
+ refetchOnMount: 'always',
376
+ staleTime: 0,
377
+ retry: 1,
378
+ });
379
+ return result;
380
+ }
262
381
  /**
263
382
  * ChatBase component - Universal chat panel supporting store, protocol, and custom modes
264
383
  */
265
- export function ChatBase({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions,
384
+ export function ChatBase({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions, chatViewMode, onChatViewModeChange,
266
385
  // Mode selection
267
386
  useStore: useStoreMode = true, protocol: protocolProp, agentRuntimeConfig, onSendMessage, enableStreaming = false,
268
387
  // Extended props
269
- brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
388
+ brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
270
389
  // Identity/Authorization props
271
390
  onAuthorizationRequired, connectedIdentities,
272
391
  // Conversation persistence
273
- runtimeId, historyEndpoint, historyAuthToken, }) {
274
- // Convert agentRuntimeConfig to protocol if provided
392
+ runtimeId, historyEndpoint, historyAuthToken,
393
+ // Pending prompt
394
+ pendingPrompt, }) {
275
395
  const protocol = agentRuntimeConfig
276
396
  ? {
277
397
  type: agentRuntimeConfig.protocol || 'ag-ui',
@@ -288,25 +408,30 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
288
408
  const existingQueryClient = useContext(QueryClientContext);
289
409
  // If no QueryClient is available, wrap with our internal provider
290
410
  if (!existingQueryClient) {
291
- return (_jsx(QueryClientProvider, { client: internalQueryClient, children: _jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken }) }));
411
+ return (_jsx(QueryClientProvider, { client: internalQueryClient, children: _jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, chatViewMode: chatViewMode, onChatViewModeChange: onChatViewModeChange, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, showInformation: showInformation, onInformationClick: onInformationClick, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt }) }));
292
412
  }
293
413
  // QueryClient already available, render inner component directly
294
- return (_jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken }));
414
+ return (_jsx(ChatBaseInner, { title: title, showHeader: showHeader, showTokenUsage: showTokenUsage, showLoadingIndicator: showLoadingIndicator, showErrors: showErrors, showInput: showInput, showModelSelector: showModelSelector, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, codemodeEnabled: codemodeEnabled, initialModel: initialModel, availableModels: availableModels, mcpServers: mcpServers, initialSkills: initialSkills, className: className, loadingState: loadingState, headerActions: headerActions, chatViewMode: chatViewMode, onChatViewModeChange: onChatViewModeChange, useStore: effectiveUseStoreMode, protocol: protocol, onSendMessage: onSendMessage, enableStreaming: enableStreaming, brandIcon: brandIcon, avatarConfig: avatarConfig, headerButtons: headerButtons, showPoweredBy: showPoweredBy, poweredByProps: poweredByProps, emptyState: emptyState, renderToolResult: renderToolResult, footerContent: footerContent, showInformation: showInformation, onInformationClick: onInformationClick, headerContent: headerContent, children: children, borderRadius: borderRadius, backgroundColor: backgroundColor, border: border, boxShadow: boxShadow, compact: compact, placeholder: placeholder, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, onClear: onClear, onMessagesChange: onMessagesChange, autoFocus: autoFocus, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, focusTrigger: focusTrigger, frontendTools: frontendTools, onAuthorizationRequired: onAuthorizationRequired, connectedIdentities: connectedIdentities, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt }));
295
415
  }
296
416
  /**
297
417
  * Inner ChatBase component - contains all the actual logic
298
418
  */
299
- function ChatBaseInner({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions,
419
+ function ChatBaseInner({ title, showHeader = false, showTokenUsage = true, showLoadingIndicator = true, showErrors = true, showInput = true, showModelSelector = false, showToolsMenu = false, showSkillsMenu = false, codemodeEnabled = false, initialModel, availableModels, mcpServers, initialSkills, className, loadingState, headerActions, chatViewMode, onChatViewModeChange,
300
420
  // Mode selection
301
421
  useStore: useStoreMode = true, protocol, onSendMessage, enableStreaming = false,
302
422
  // Extended props
303
- brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
423
+ brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, footerContent, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools,
304
424
  // Identity/Authorization props
305
425
  onAuthorizationRequired, connectedIdentities,
306
426
  // Conversation persistence
307
- runtimeId, historyEndpoint, historyAuthToken, }) {
308
- // Ensure Primer's default portal has high z-index for ActionMenu overlays
427
+ runtimeId, historyEndpoint, historyAuthToken,
428
+ // Pending prompt
429
+ pendingPrompt, }) {
309
430
  useHighZIndexPortal();
431
+ // Stabilize the protocol reference so that the adapter-init effect only
432
+ // re-runs when the protocol *contents* actually change, not just when the
433
+ // parent re-renders with a new object literal that has the same values.
434
+ const protocolKey = protocol ? JSON.stringify(protocol) : '';
310
435
  // Store (optional for message persistence)
311
436
  const clearStoreMessages = useChatStore(state => state.clearMessages);
312
437
  // Check if protocol is A2A (doesn't support per-request model override)
@@ -317,7 +442,12 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
317
442
  const [isStreaming, setIsStreaming] = useState(false);
318
443
  const [error, setError] = useState(null);
319
444
  const [input, setInput] = useState('');
320
- // Model and tools state
445
+ // History-loaded flag true immediately when there is nothing to fetch
446
+ const [historyLoaded, setHistoryLoaded] = useState(!runtimeId);
447
+ // Adapter-ready flag — flipped to true once the protocol adapter is initialised
448
+ const [adapterReady, setAdapterReady] = useState(false);
449
+ // Guard so the pending prompt is sent at most once
450
+ const pendingPromptSentRef = useRef(false);
321
451
  const [selectedModel, setSelectedModel] = useState('');
322
452
  // enabledTools tracks which MCP server tools are enabled
323
453
  // Format: Map<serverId, Set<toolName>>
@@ -336,15 +466,26 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
336
466
  // Gated by showTokenUsage (not showHeader), so usage is visible even without a title bar.
337
467
  const contextSnapshotQuery = useContextSnapshotQuery(Boolean(protocol?.enableConfigQuery) && showTokenUsage, protocol?.configEndpoint, protocol?.agentId, protocol?.authToken);
338
468
  const agentUsage = contextSnapshotQuery.data;
469
+ // Sandbox status query — polls sandbox execution state for the header indicator.
470
+ // Only active when codemode is enabled and there's a config endpoint.
471
+ const sandboxStatusQuery = useSandboxStatusQuery(Boolean(protocol?.enableConfigQuery) && codemodeEnabled && showHeader, protocol?.configEndpoint, protocol?.authToken);
472
+ const sandboxStatus = sandboxStatusQuery.data;
339
473
  // Refs
340
474
  const adapterRef = useRef(null);
341
475
  const unsubscribeRef = useRef(null);
342
476
  const toolCallsRef = useRef(new Map());
477
+ // Track the number of in-flight frontend tool executions.
478
+ // While > 0, isLoading must stay true (the agent turn is not finished).
479
+ const pendingToolExecutionsRef = useRef(0);
343
480
  const currentAssistantMessageRef = useRef(null);
344
481
  const threadIdRef = useRef(generateMessageId());
345
482
  const messagesEndRef = useRef(null);
346
483
  const inputRef = useRef(null);
347
484
  const abortControllerRef = useRef(null);
485
+ // State for context pie chart overlay
486
+ const [contextOverlayOpen, setContextOverlayOpen] = useState(false);
487
+ const contextAnchorRef = useRef(null);
488
+ const hoverTimeoutRef = useRef(null);
348
489
  // Use a ref for connectedIdentities to avoid infinite loops in useCallback
349
490
  // (the array reference changes on every render even if contents are the same)
350
491
  const connectedIdentitiesRef = useRef(connectedIdentities);
@@ -418,15 +559,16 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
418
559
  if ((configQuery.data || availableModels) && !selectedModel) {
419
560
  // Use availableModels override if provided, otherwise use config models
420
561
  const modelsList = availableModels || configQuery.data?.models || [];
421
- // Use initialModel if provided, otherwise select first available model
422
- if (initialModel) {
423
- // Check if the initial model exists in the models list
424
- const modelExists = modelsList.some(m => m.id === initialModel);
562
+ // Priority: initialModel prop > defaultModel from config > first available model
563
+ const preferredModel = initialModel || configQuery.data?.defaultModel;
564
+ if (preferredModel) {
565
+ // Check if the preferred model exists in the models list
566
+ const modelExists = modelsList.some(m => m.id === preferredModel);
425
567
  if (modelExists) {
426
- setSelectedModel(initialModel);
568
+ setSelectedModel(preferredModel);
427
569
  }
428
570
  else {
429
- // Fallback to first available model if initialModel not found
571
+ // Fallback to first available model if preferred model not found
430
572
  const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
431
573
  const firstModel = firstAvailableModel || modelsList[0];
432
574
  if (firstModel) {
@@ -435,7 +577,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
435
577
  }
436
578
  }
437
579
  else {
438
- // No initialModel provided, select first available model
580
+ // No preferred model, select first available model
439
581
  const firstAvailableModel = modelsList.find(m => m.isAvailable !== false);
440
582
  const firstModel = firstAvailableModel || modelsList[0];
441
583
  if (firstModel) {
@@ -596,18 +738,26 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
596
738
  if (storedMessages.length > 0) {
597
739
  setDisplayItems(storedMessages);
598
740
  }
741
+ setHistoryLoaded(true);
599
742
  return;
600
743
  }
601
744
  // Mark as fetching to prevent duplicate requests
602
745
  store.setFetching(runtimeId, true);
603
746
  // Build the history endpoint URL
604
- const endpoint = historyEndpoint ||
605
- (protocol?.endpoint ? `${protocol.endpoint}/history` : null);
747
+ let endpoint = historyEndpoint ||
748
+ (protocol?.endpoint ? `${protocol.endpoint}/api/v1/history` : null);
606
749
  if (!endpoint) {
607
750
  console.warn('[ChatBase] No history endpoint available for runtimeId:', runtimeId);
608
751
  store.markFetched(runtimeId);
752
+ setHistoryLoaded(true);
609
753
  return;
610
754
  }
755
+ // Append agent_id query param if the protocol has an agentId
756
+ // and the URL doesn't already include one
757
+ if (protocol?.agentId && !endpoint.includes('agent_id=')) {
758
+ const separator = endpoint.includes('?') ? '&' : '?';
759
+ endpoint = `${endpoint}${separator}agent_id=${encodeURIComponent(protocol.agentId)}`;
760
+ }
611
761
  // Fetch conversation history from server
612
762
  const fetchHistory = async () => {
613
763
  try {
@@ -627,17 +777,51 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
627
777
  throw new Error(`Failed to fetch history: ${response.status} ${response.statusText}`);
628
778
  }
629
779
  const data = await response.json();
630
- const messages = data.messages || [];
631
- // Store in memory and update display
780
+ // Map server history messages to ChatMessage format.
781
+ // The server may return toolCalls in either the legacy {id, name, arguments}
782
+ // shape or the correct ToolCallContentPart {toolCallId, toolName, args} shape.
783
+ // Normalize both to ToolCallContentPart so the AG-UI adapter can serialize them.
784
+ const messages = (data.messages || []).map((msg) => {
785
+ if (msg.toolCalls && Array.isArray(msg.toolCalls)) {
786
+ msg.toolCalls = msg.toolCalls.map((tc) => {
787
+ // If already in ToolCallContentPart format, keep as-is
788
+ if (tc.toolCallId && tc.toolName)
789
+ return tc;
790
+ // Legacy format: {id, name, arguments} → ToolCallContentPart
791
+ let parsedArgs = tc.args ?? tc.arguments ?? {};
792
+ if (typeof parsedArgs === 'string') {
793
+ try {
794
+ parsedArgs = JSON.parse(parsedArgs);
795
+ }
796
+ catch {
797
+ parsedArgs = {};
798
+ }
799
+ }
800
+ return {
801
+ type: 'tool-call',
802
+ toolCallId: tc.toolCallId ?? tc.id ?? tc.tool_call_id ?? '',
803
+ toolName: tc.toolName ?? tc.name ?? tc.tool_name ?? '',
804
+ args: parsedArgs,
805
+ status: tc.status ?? 'completed',
806
+ };
807
+ });
808
+ }
809
+ return msg;
810
+ });
811
+ // Store in memory and convert to display items
632
812
  if (messages.length > 0) {
633
813
  store.setMessages(runtimeId, messages);
634
- setDisplayItems(messages);
814
+ // Convert to display items: expand tool calls, merge tool results
815
+ const items = convertHistoryToDisplayItems(messages);
816
+ setDisplayItems(items);
635
817
  }
636
818
  store.markFetched(runtimeId);
819
+ setHistoryLoaded(true);
637
820
  }
638
821
  catch (err) {
639
822
  console.error('[ChatBase] Failed to fetch conversation history:', err);
640
823
  store.markFetched(runtimeId);
824
+ setHistoryLoaded(true);
641
825
  }
642
826
  };
643
827
  fetchHistory();
@@ -691,6 +875,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
691
875
  if (!adapter)
692
876
  return;
693
877
  adapterRef.current = adapter;
878
+ setAdapterReady(true);
694
879
  // Subscribe to protocol events
695
880
  unsubscribeRef.current = adapter.subscribe((event) => {
696
881
  switch (event.type) {
@@ -730,10 +915,35 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
730
915
  const newMessage = createAssistantMessage(typeof contentStr === 'string' ? contentStr : '');
731
916
  newMessage.id = event.message.id || newMessage.id;
732
917
  currentAssistantMessageRef.current = newMessage;
733
- setDisplayItems(prev => [...prev, newMessage]);
734
- // Add message to store
918
+ // Guard against duplicates: if an item with the same ID
919
+ // already exists (e.g. continuation arriving after
920
+ // handleSend's finally cleared currentAssistantMessageRef),
921
+ // update it in place instead of appending a second copy.
922
+ setDisplayItems(prev => {
923
+ const existingIdx = prev.findIndex(item => !isToolCallMessage(item) && item.id === newMessage.id);
924
+ if (existingIdx >= 0) {
925
+ const newItems = [...prev];
926
+ newItems[existingIdx] = {
927
+ ...newItems[existingIdx],
928
+ content: event.message?.content ?? '',
929
+ };
930
+ return newItems;
931
+ }
932
+ return [...prev, newMessage];
933
+ });
934
+ // Add message to store (only if truly new)
735
935
  if (useStoreMode) {
736
- useChatStore.getState().addMessage(newMessage);
936
+ const existingInStore = useChatStore
937
+ .getState()
938
+ .messages.find(m => m.id === newMessage.id);
939
+ if (existingInStore) {
940
+ useChatStore.getState().updateMessage(newMessage.id, {
941
+ content: event.message?.content ?? '',
942
+ });
943
+ }
944
+ else {
945
+ useChatStore.getState().addMessage(newMessage);
946
+ }
737
947
  }
738
948
  }
739
949
  }
@@ -767,6 +977,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
767
977
  const toolHandler = frontendTool?.handler;
768
978
  if (toolHandler && Object.keys(args).length > 0) {
769
979
  // Execute frontend tool
980
+ pendingToolExecutionsRef.current++;
770
981
  (async () => {
771
982
  try {
772
983
  const result = await toolHandler(updatedToolCall.args);
@@ -803,6 +1014,14 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
803
1014
  ? errorToolCall
804
1015
  : item));
805
1016
  }
1017
+ finally {
1018
+ pendingToolExecutionsRef.current--;
1019
+ if (pendingToolExecutionsRef.current <= 0) {
1020
+ pendingToolExecutionsRef.current = 0;
1021
+ setIsLoading(false);
1022
+ setIsStreaming(false);
1023
+ }
1024
+ }
806
1025
  })();
807
1026
  }
808
1027
  }
@@ -824,6 +1043,7 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
824
1043
  const frontendTool = frontendTools?.find(t => t.name === toolName);
825
1044
  const toolHandler = frontendTool?.handler;
826
1045
  if (toolHandler && Object.keys(args).length > 0) {
1046
+ pendingToolExecutionsRef.current++;
827
1047
  (async () => {
828
1048
  try {
829
1049
  const result = await toolHandler(args);
@@ -860,6 +1080,14 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
860
1080
  ? errorToolCall
861
1081
  : item));
862
1082
  }
1083
+ finally {
1084
+ pendingToolExecutionsRef.current--;
1085
+ if (pendingToolExecutionsRef.current <= 0) {
1086
+ pendingToolExecutionsRef.current = 0;
1087
+ setIsLoading(false);
1088
+ setIsStreaming(false);
1089
+ }
1090
+ }
863
1091
  })();
864
1092
  }
865
1093
  }
@@ -987,21 +1215,25 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
987
1215
  unsubscribeRef.current?.();
988
1216
  adapterRef.current?.disconnect();
989
1217
  };
990
- // Note: frontendTools is accessed via ref-like closure, not as reactive dependency
1218
+ // protocolKey (JSON-serialised) replaces the protocol object reference so
1219
+ // that parent re-renders producing a new-but-identical protocol object
1220
+ // don't tear down and re-create the adapter mid-request.
1221
+ // frontendTools is accessed via ref-like closure, not as reactive dependency
991
1222
  // eslint-disable-next-line react-hooks/exhaustive-deps
992
- }, [protocol, renderToolResult, onStateUpdate, useStoreMode]);
1223
+ }, [protocolKey, renderToolResult, onStateUpdate, useStoreMode]);
993
1224
  // Auto-scroll to bottom
994
1225
  useEffect(() => {
995
1226
  messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
996
1227
  }, [displayItems]);
997
- // Handle sending message in protocol mode or custom mode
998
- const handleSend = useCallback(async () => {
999
- if (!input.trim() || isLoading)
1228
+ // Handle sending message in protocol mode or custom mode.
1229
+ // An optional messageOverride bypasses the input state (used by pendingPrompt).
1230
+ const handleSend = useCallback(async (messageOverride) => {
1231
+ const messageContent = (messageOverride ?? input).trim();
1232
+ if (!messageContent || isLoading)
1000
1233
  return;
1001
1234
  // Need either an adapter (protocol mode) or onSendMessage handler (custom mode)
1002
1235
  if (!adapterRef.current && !onSendMessage)
1003
1236
  return;
1004
- const messageContent = input.trim();
1005
1237
  const userMessage = createUserMessage(messageContent);
1006
1238
  const currentMessages = displayItems.filter((item) => !isToolCallMessage(item));
1007
1239
  const allMessages = [...currentMessages, userMessage];
@@ -1052,9 +1284,9 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1052
1284
  ? { ...item, content: fullResponse }
1053
1285
  : item));
1054
1286
  if (useStoreMode) {
1055
- useChatStore
1056
- .getState()
1057
- .updateMessage(assistantMessageId, { content: fullResponse });
1287
+ useChatStore.getState().updateMessage(assistantMessageId, {
1288
+ content: fullResponse,
1289
+ });
1058
1290
  useChatStore.getState().stopStreaming();
1059
1291
  }
1060
1292
  },
@@ -1065,9 +1297,9 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1065
1297
  ? { ...item, content: errorContent }
1066
1298
  : item));
1067
1299
  if (useStoreMode) {
1068
- useChatStore
1069
- .getState()
1070
- .updateMessage(assistantMessageId, { content: errorContent });
1300
+ useChatStore.getState().updateMessage(assistantMessageId, {
1301
+ content: errorContent,
1302
+ });
1071
1303
  useChatStore.getState().stopStreaming();
1072
1304
  }
1073
1305
  setError(error);
@@ -1121,8 +1353,13 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1121
1353
  }
1122
1354
  }
1123
1355
  finally {
1124
- setIsLoading(false);
1125
- setIsStreaming(false);
1356
+ // Only clear loading state if no frontend tool executions are
1357
+ // still in flight. When tools are pending, sendToolResult will
1358
+ // trigger a continuation that eventually clears isLoading.
1359
+ if (pendingToolExecutionsRef.current <= 0) {
1360
+ setIsLoading(false);
1361
+ setIsStreaming(false);
1362
+ }
1126
1363
  currentAssistantMessageRef.current = null;
1127
1364
  abortControllerRef.current = null;
1128
1365
  }
@@ -1138,6 +1375,19 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1138
1375
  getEnabledMcpToolNames,
1139
1376
  getEnabledSkillIds,
1140
1377
  ]);
1378
+ // Send the pending prompt once history is loaded and the
1379
+ // adapter (or custom handler) is available.
1380
+ useEffect(() => {
1381
+ if (!pendingPrompt || pendingPromptSentRef.current)
1382
+ return;
1383
+ if (!historyLoaded)
1384
+ return;
1385
+ if (!adapterReady && !onSendMessage)
1386
+ return;
1387
+ pendingPromptSentRef.current = true;
1388
+ // Use a microtask to ensure the adapter subscription is fully wired.
1389
+ queueMicrotask(() => handleSend(pendingPrompt));
1390
+ }, [pendingPrompt, historyLoaded, adapterReady, handleSend, onSendMessage]);
1141
1391
  // Handle stop
1142
1392
  const handleStop = useCallback(() => {
1143
1393
  // Abort custom mode request
@@ -1148,20 +1398,16 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1148
1398
  if (useStoreMode) {
1149
1399
  useChatStore.getState().stopStreaming();
1150
1400
  }
1401
+ // Reset pending tool counter so loading can clear
1402
+ pendingToolExecutionsRef.current = 0;
1151
1403
  setIsLoading(false);
1152
1404
  setIsStreaming(false);
1153
1405
  }, [useStoreMode]);
1154
- // Handle key press
1155
- const handleKeyDown = useCallback((e) => {
1156
- if (e.key === 'Enter' && !e.shiftKey) {
1157
- e.preventDefault();
1158
- handleSend();
1159
- }
1160
- }, [handleSend]);
1161
1406
  // Handle new chat
1162
1407
  const handleNewChat = useCallback(() => {
1163
1408
  setDisplayItems([]);
1164
1409
  toolCallsRef.current.clear();
1410
+ pendingToolExecutionsRef.current = 0;
1165
1411
  setInput('');
1166
1412
  threadIdRef.current = generateMessageId();
1167
1413
  if (useStoreMode) {
@@ -1190,6 +1436,24 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1190
1436
  headerButtons?.onClear?.();
1191
1437
  }
1192
1438
  }, [clearStoreMessages, onClear, headerButtons, useStoreMode, runtimeId]);
1439
+ // Handle sandbox interrupt
1440
+ const handleSandboxInterrupt = useCallback(async () => {
1441
+ if (!protocol?.configEndpoint)
1442
+ return;
1443
+ const interruptUrl = `${getApiBaseFromConfig(protocol.configEndpoint)}/configure/sandbox/interrupt`;
1444
+ try {
1445
+ const headers = { 'Content-Type': 'application/json' };
1446
+ if (protocol.authToken) {
1447
+ headers['Authorization'] = `Bearer ${protocol.authToken}`;
1448
+ }
1449
+ await fetch(interruptUrl, { method: 'POST', headers });
1450
+ // Refetch status immediately so the icon updates
1451
+ sandboxStatusQuery.refetch();
1452
+ }
1453
+ catch (e) {
1454
+ // Interrupt is best-effort
1455
+ }
1456
+ }, [protocol?.configEndpoint, protocol?.authToken, sandboxStatusQuery]);
1193
1457
  // Not ready yet (store mode only)
1194
1458
  if (!ready) {
1195
1459
  return (_jsx(Box, { className: className, sx: {
@@ -1219,7 +1483,60 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1219
1483
  alignItems: 'center',
1220
1484
  justifyContent: 'space-between',
1221
1485
  p: padding,
1222
- }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), title && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, fontWeight: 'semibold' }, children: title })), headerContent] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: handleNewChat })), headerButtons?.showClear && messages.length > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: handleClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), headerActions] })] }) }));
1486
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), title && (_jsx(Heading, { as: "h3", sx: { fontSize: 2, fontWeight: 'semibold' }, children: title })), headerContent, showInformation && (_jsx(IconButton, { icon: InfoIcon, "aria-label": "Information", variant: "invisible", size: "small", onClick: onInformationClick }))] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [sandboxStatus?.available &&
1487
+ sandboxStatus?.sandbox_running &&
1488
+ (sandboxStatus.is_executing ? (_jsx(IconButton, { icon: SquareFillIcon, "aria-label": "Interrupt code execution", variant: "invisible", size: "small", sx: { color: 'danger.fg' }, onClick: handleSandboxInterrupt })) : (_jsx(Box, { sx: {
1489
+ display: 'flex',
1490
+ alignItems: 'center',
1491
+ justifyContent: 'center',
1492
+ width: 28,
1493
+ height: 28,
1494
+ color: 'fg.subtle',
1495
+ }, title: "Code sandbox ready", children: _jsx(CircleIcon, { size: 12 }) }))), headerButtons?.showNewChat && (_jsx(IconButton, { icon: PlusIcon, "aria-label": "New chat", variant: "invisible", size: "small", onClick: handleNewChat })), headerButtons?.showClear && messages.length > 0 && (_jsx(IconButton, { icon: TrashIcon, "aria-label": "Clear messages", variant: "invisible", size: "small", onClick: handleClear })), headerButtons?.showSettings && (_jsx(IconButton, { icon: GearIcon, "aria-label": "Settings", variant: "invisible", size: "small", onClick: headerButtons.onSettings })), chatViewMode && onChatViewModeChange && (_jsx(Box, { sx: {
1496
+ display: 'inline-flex',
1497
+ alignItems: 'center',
1498
+ bg: 'neutral.muted',
1499
+ borderRadius: '6px',
1500
+ p: '2px',
1501
+ gap: '1px',
1502
+ }, children: [
1503
+ {
1504
+ mode: 'floating',
1505
+ icon: CommentDiscussionIcon,
1506
+ label: 'Full-height popup',
1507
+ },
1508
+ {
1509
+ mode: 'floating-small',
1510
+ icon: DeviceMobileIcon,
1511
+ label: 'Floating popup',
1512
+ },
1513
+ {
1514
+ mode: 'sidebar',
1515
+ icon: SidebarExpandIcon,
1516
+ label: 'Sidebar panel',
1517
+ },
1518
+ ].map(({ mode, icon: ModeIcon, label }) => (_jsx(Box, { as: "button", "aria-label": label, title: label, onClick: () => onChatViewModeChange(mode), sx: {
1519
+ display: 'inline-flex',
1520
+ alignItems: 'center',
1521
+ justifyContent: 'center',
1522
+ width: 26,
1523
+ height: 24,
1524
+ borderRadius: '4px',
1525
+ border: 'none',
1526
+ cursor: 'pointer',
1527
+ bg: chatViewMode === mode
1528
+ ? 'canvas.default'
1529
+ : 'transparent',
1530
+ boxShadow: chatViewMode === mode ? 'shadow.small' : 'none',
1531
+ color: chatViewMode === mode ? 'fg.default' : 'fg.muted',
1532
+ transition: 'all 0.15s ease',
1533
+ '&:hover': {
1534
+ color: 'fg.default',
1535
+ bg: chatViewMode === mode
1536
+ ? 'canvas.default'
1537
+ : 'neutral.subtle',
1538
+ },
1539
+ }, children: _jsx(ModeIcon, { size: 14 }) }, mode))) })), headerActions] })] }) }));
1223
1540
  };
1224
1541
  // Render token usage bar between input and selectors
1225
1542
  const renderTokenUsage = () => {
@@ -1235,6 +1552,109 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1235
1552
  agentUsage.sessionUsage.outputTokens > 0);
1236
1553
  if (!hasContext)
1237
1554
  return null;
1555
+ // Build pie chart data from distribution or fallback to fields
1556
+ const usedTokens = agentUsage.totalTokens;
1557
+ const windowTokens = agentUsage.contextWindow;
1558
+ const freeTokens = Math.max(0, windowTokens - usedTokens);
1559
+ const pct = windowTokens > 0 ? (usedTokens / windowTokens) * 100 : 0;
1560
+ // Build category breakdown from distribution or individual fields
1561
+ const categories = [];
1562
+ if (agentUsage.distribution?.children?.length) {
1563
+ const colorMap = {
1564
+ 'System Prompts': '#8250df',
1565
+ 'Tool Definitions': '#bf8700',
1566
+ 'User Messages': '#0969da',
1567
+ 'Assistant Messages': '#1a7f37',
1568
+ 'Tool Usage': '#cf222e',
1569
+ };
1570
+ for (const child of agentUsage.distribution.children) {
1571
+ categories.push({
1572
+ name: child.name,
1573
+ value: child.value,
1574
+ color: colorMap[child.name] || '#6e7781',
1575
+ });
1576
+ }
1577
+ }
1578
+ else {
1579
+ // Fallback: build from individual fields
1580
+ if (agentUsage.systemPromptTokens > 0) {
1581
+ categories.push({
1582
+ name: 'System Prompts',
1583
+ value: agentUsage.systemPromptTokens,
1584
+ color: '#8250df',
1585
+ });
1586
+ }
1587
+ if (agentUsage.toolTokens > 0) {
1588
+ categories.push({
1589
+ name: 'Tool Definitions',
1590
+ value: agentUsage.toolTokens,
1591
+ color: '#bf8700',
1592
+ });
1593
+ }
1594
+ const messageTokens = (agentUsage.userMessageTokens || 0) +
1595
+ (agentUsage.assistantMessageTokens || 0);
1596
+ if (messageTokens > 0) {
1597
+ categories.push({
1598
+ name: 'Messages',
1599
+ value: messageTokens,
1600
+ color: '#0969da',
1601
+ });
1602
+ }
1603
+ const toolResultTokens = (agentUsage.toolCallTokens || 0) + (agentUsage.toolReturnTokens || 0);
1604
+ if (toolResultTokens > 0) {
1605
+ categories.push({
1606
+ name: 'Tool Results',
1607
+ value: toolResultTokens,
1608
+ color: '#cf222e',
1609
+ });
1610
+ }
1611
+ }
1612
+ // Tiny filled pie chart options
1613
+ const pieColor = pct > 90 ? '#cf222e' : pct > 70 ? '#bf8700' : '#0969da';
1614
+ const freeSliceColor = 'var(--bgColor-muted, #f6f8fa)';
1615
+ const freeSliceOverlayColor = 'var(--borderColor-default, #d1d9e0)';
1616
+ const miniPieOption = {
1617
+ animation: false,
1618
+ series: [
1619
+ {
1620
+ type: 'pie',
1621
+ radius: [0, '90%'],
1622
+ center: ['50%', '50%'],
1623
+ silent: true,
1624
+ label: { show: false },
1625
+ labelLine: { show: false },
1626
+ data: [
1627
+ { value: usedTokens, itemStyle: { color: pieColor } },
1628
+ { value: freeTokens, itemStyle: { color: freeSliceColor } },
1629
+ ],
1630
+ },
1631
+ ],
1632
+ };
1633
+ // Overlay detail pie options (donut for category breakdown)
1634
+ const overlayPieOption = {
1635
+ animation: false,
1636
+ series: [
1637
+ {
1638
+ type: 'pie',
1639
+ radius: ['45%', '80%'],
1640
+ center: ['50%', '50%'],
1641
+ silent: true,
1642
+ label: { show: false },
1643
+ labelLine: { show: false },
1644
+ itemStyle: {
1645
+ borderColor: 'var(--bgColor-default, #ffffff)',
1646
+ borderWidth: 1,
1647
+ },
1648
+ data: [
1649
+ ...categories.map(c => ({
1650
+ value: c.value,
1651
+ itemStyle: { color: c.color },
1652
+ })),
1653
+ { value: freeTokens, itemStyle: { color: freeSliceOverlayColor } },
1654
+ ],
1655
+ },
1656
+ ],
1657
+ };
1238
1658
  return (_jsxs(Box, { sx: {
1239
1659
  display: 'flex',
1240
1660
  alignItems: 'center',
@@ -1244,10 +1664,76 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1244
1664
  px: padding,
1245
1665
  bg: 'canvas.subtle',
1246
1666
  flexWrap: 'nowrap',
1247
- overflow: 'hidden',
1667
+ overflow: 'visible',
1248
1668
  whiteSpace: 'nowrap',
1249
1669
  minWidth: 0,
1250
- }, children: [_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'semibold', color: 'fg.default', fontSize: 0 }, children: formatTokenCount(agentUsage.totalTokens) }), ' / ', formatTokenCount(agentUsage.contextWindow), ' ctx'] }), hasSession && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· ', formatTokenCount(agentUsage.sessionUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.sessionUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] })), hasTurn && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· turn ', formatTokenCount(agentUsage.turnUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.turnUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] }))] }));
1670
+ }, children: [_jsxs(Box, { sx: { position: 'relative', flexShrink: 0 }, onMouseEnter: () => {
1671
+ if (hoverTimeoutRef.current)
1672
+ clearTimeout(hoverTimeoutRef.current);
1673
+ hoverTimeoutRef.current = setTimeout(() => setContextOverlayOpen(true), 150);
1674
+ }, onMouseLeave: () => {
1675
+ if (hoverTimeoutRef.current)
1676
+ clearTimeout(hoverTimeoutRef.current);
1677
+ hoverTimeoutRef.current = setTimeout(() => setContextOverlayOpen(false), 250);
1678
+ }, children: [_jsx(Box, { ref: contextAnchorRef, sx: {
1679
+ cursor: 'pointer',
1680
+ width: 20,
1681
+ height: 20,
1682
+ display: 'flex',
1683
+ alignItems: 'center',
1684
+ justifyContent: 'center',
1685
+ border: '1px solid',
1686
+ borderColor: 'border.default',
1687
+ borderRadius: '50%',
1688
+ }, children: _jsx(ReactECharts, { option: miniPieOption, style: { width: 18, height: 18 }, opts: { renderer: 'svg' } }) }), contextOverlayOpen && (_jsxs(Box, { sx: {
1689
+ position: 'absolute',
1690
+ bottom: '100%',
1691
+ left: 0,
1692
+ mb: 1,
1693
+ p: 3,
1694
+ width: 260,
1695
+ bg: 'canvas.overlay',
1696
+ borderRadius: 2,
1697
+ boxShadow: 'shadow.large',
1698
+ border: '1px solid',
1699
+ borderColor: 'border.default',
1700
+ zIndex: 100,
1701
+ }, children: [_jsx(Text, { sx: {
1702
+ fontWeight: 'bold',
1703
+ fontSize: 1,
1704
+ color: 'fg.default',
1705
+ display: 'block',
1706
+ mb: 2,
1707
+ }, children: "Context Window" }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block', mb: 1 }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'semibold', color: 'fg.default' }, children: formatTokenCount(usedTokens) }), ' / ', formatTokenCount(windowTokens), ' tokens'] }), _jsxs(Text, { sx: {
1708
+ fontSize: 0,
1709
+ color: pct > 90
1710
+ ? 'danger.fg'
1711
+ : pct > 70
1712
+ ? 'attention.fg'
1713
+ : 'fg.muted',
1714
+ fontWeight: 'semibold',
1715
+ display: 'block',
1716
+ mb: 2,
1717
+ }, children: ['• ', pct.toFixed(0), "%"] }), _jsx(Box, { sx: { display: 'flex', justifyContent: 'center', mb: 2 }, children: _jsx(ReactECharts, { option: overlayPieOption, style: { width: 80, height: 80 }, opts: { renderer: 'svg' } }) }), _jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 1 }, children: categories.map(cat => {
1718
+ const catPct = usedTokens > 0 ? (cat.value / usedTokens) * 100 : 0;
1719
+ return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Box, { sx: {
1720
+ width: 8,
1721
+ height: 8,
1722
+ borderRadius: '50%',
1723
+ bg: cat.color,
1724
+ flexShrink: 0,
1725
+ } }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', flex: 1 }, children: cat.name }), _jsxs(Text, { sx: {
1726
+ fontSize: 0,
1727
+ color: 'fg.default',
1728
+ fontWeight: 'semibold',
1729
+ }, children: [catPct.toFixed(1), "%"] })] }, cat.name));
1730
+ }) }), pct > 70 && (_jsx(Text, { sx: {
1731
+ fontSize: 0,
1732
+ color: 'attention.fg',
1733
+ display: 'block',
1734
+ mt: 2,
1735
+ fontStyle: 'italic',
1736
+ }, children: "Quality may decline as limit nears." }))] }))] }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: [_jsx(Text, { as: "span", sx: { fontWeight: 'semibold', color: 'fg.default', fontSize: 0 }, children: formatTokenCount(agentUsage.totalTokens) }), ' / ', formatTokenCount(agentUsage.contextWindow), ' ctx'] }), hasSession && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· ', formatTokenCount(agentUsage.sessionUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.sessionUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] })), hasTurn && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted', flexShrink: 0 }, children: ['· turn ', formatTokenCount(agentUsage.turnUsage.inputTokens), _jsx(Text, { as: "span", sx: { color: 'success.fg', fontSize: 0 }, children: '▲' }), ' ', formatTokenCount(agentUsage.turnUsage.outputTokens), _jsx(Text, { as: "span", sx: { color: 'attention.fg', fontSize: 0 }, children: '▼' })] }))] }));
1251
1737
  };
1252
1738
  // Render empty state
1253
1739
  const renderEmptyState = () => {
@@ -1516,88 +2002,12 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1516
2002
  ? 'accent.emphasis'
1517
2003
  : 'canvas.subtle',
1518
2004
  color: isUser ? 'fg.onEmphasis' : 'fg.default',
1519
- // Streamdown code block styling
1520
- // Code block container
1521
- '& [data-streamdown="code-block"]': {
1522
- borderRadius: '8px',
1523
- border: '1px solid',
1524
- borderColor: 'border.default',
1525
- overflow: 'hidden',
1526
- my: 2,
1527
- },
1528
- // Code block header with language label and buttons
1529
- '& [data-streamdown="code-block-header"]': {
1530
- display: 'flex',
1531
- alignItems: 'center',
1532
- justifyContent: 'space-between',
1533
- backgroundColor: 'canvas.subtle',
1534
- padding: '8px 12px',
1535
- fontSize: '12px',
1536
- color: 'fg.muted',
1537
- },
1538
- // Style the buttons in the header
1539
- '& [data-streamdown="code-block-header"] button': {
1540
- background: 'none',
1541
- border: 'none',
1542
- cursor: 'pointer',
1543
- padding: '4px',
1544
- color: 'fg.muted',
1545
- borderRadius: '4px',
1546
- '&:hover': {
1547
- backgroundColor: 'neutral.muted',
1548
- color: 'fg.default',
1549
- },
1550
- },
1551
- // Code block body
1552
- '& [data-streamdown="code-block-body"]': {
1553
- backgroundColor: 'canvas.subtle',
1554
- padding: '12px',
1555
- margin: 0,
1556
- overflow: 'auto',
1557
- fontSize: '13px',
1558
- lineHeight: 1.5,
1559
- },
1560
- // Make each line display as a block for line breaks
1561
- '& [data-streamdown="code-block-body"] code': {
1562
- display: 'block',
1563
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
1564
- },
1565
- '& [data-streamdown="code-block-body"] code > span.block': {
1566
- display: 'block',
1567
- },
1568
- '& [data-streamdown="code-block-body"] code > span': {
1569
- display: 'block',
1570
- },
1571
- // General pre/code styling fallback
1572
- '& pre': {
1573
- whiteSpace: 'pre-wrap',
1574
- wordBreak: 'break-word',
1575
- overflowX: 'auto',
1576
- margin: 0,
1577
- },
1578
- '& pre code': {
1579
- whiteSpace: 'pre-wrap',
1580
- },
1581
- '& code': {
1582
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
1583
- },
2005
+ ...streamdownCodeBlockStyles,
1584
2006
  }, children: isUser ? (_jsx(Text, { sx: {
1585
2007
  fontSize: 1,
1586
2008
  whiteSpace: 'pre-wrap',
1587
2009
  wordBreak: 'break-word',
1588
- }, children: getMessageText(message) })) : (_jsx(Box, { sx: {
1589
- fontSize: 1,
1590
- lineHeight: 1.5,
1591
- '& ul, & ol': {
1592
- marginTop: '0.5em',
1593
- marginBottom: '0.5em',
1594
- paddingInlineStart: '1.25em',
1595
- listStylePosition: 'inside',
1596
- },
1597
- '& li': {
1598
- paddingInlineStart: '0.25em',
1599
- },
1600
- }, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
2010
+ }, children: getMessageText(message) })) : (_jsx(Box, { sx: streamdownMarkdownStyles, children: _jsx(Streamdown, { children: getMessageText(message) || (isStreaming ? '...' : '') }) })) })] }) }, message.id));
1601
2011
  }), showLoadingIndicator && (isLoading || isStreaming) && (_jsx(Box, { sx: {
1602
2012
  display: 'flex',
1603
2013
  alignItems: 'flex-start',
@@ -1677,26 +2087,11 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1677
2087
  },
1678
2088
  } })] })] }) })), _jsx("div", { ref: messagesEndRef })] }));
1679
2089
  };
1680
- // Render protocol mode input
1681
- const renderProtocolInput = () => {
2090
+ // Render input prompt
2091
+ const renderInputPrompt = () => {
1682
2092
  const availableTools = configQuery.data?.builtinTools || [];
1683
2093
  const models = availableModels || configQuery.data?.models || [];
1684
- return (_jsxs(Box, { children: [_jsx(Box, { sx: {
1685
- p: padding,
1686
- borderTop: '1px solid',
1687
- borderColor: 'border.default',
1688
- bg: 'canvas.subtle',
1689
- }, children: _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'flex-end' }, children: [_jsx(Textarea, { ref: inputRef, value: input, onChange: e => {
1690
- setInput(e.target.value);
1691
- // Height adjustment happens via useEffect watching input
1692
- }, onKeyDown: handleKeyDown, placeholder: placeholder || 'Type a message...', disabled: isLoading, sx: {
1693
- flex: 1,
1694
- resize: 'none',
1695
- minHeight: '40px',
1696
- maxHeight: '120px',
1697
- overflow: 'hidden',
1698
- transition: 'height 0.1s ease-out',
1699
- }, rows: 1 }), isLoading ? (_jsx(IconButton, { icon: SquareCircleIcon, "aria-label": "Stop", onClick: handleStop, sx: { alignSelf: 'flex-end' } })) : (_jsx(IconButton, { icon: PaperAirplaneIcon, "aria-label": "Send", onClick: handleSend, disabled: !input.trim(), sx: { alignSelf: 'flex-end' } }))] }) }), renderTokenUsage(), (showModelSelector || showToolsMenu || showSkillsMenu) &&
2094
+ return (_jsxs(Box, { children: [_jsx(InputPrompt, { placeholder: placeholder || 'Type a message...', isLoading: isLoading, onSend: () => handleSend(), onStop: handleStop, autoFocus: autoFocus, focusTrigger: focusTrigger, padding: padding, value: input, onChange: setInput }), renderTokenUsage(), (showModelSelector || showToolsMenu || showSkillsMenu) &&
1700
2095
  (configQuery.data || skillsQuery.data) && (_jsxs(Box, { sx: {
1701
2096
  display: 'flex',
1702
2097
  gap: 2,
@@ -1840,6 +2235,6 @@ runtimeId, historyEndpoint, historyAuthToken, }) {
1840
2235
  flexDirection: 'column',
1841
2236
  minHeight: '100%',
1842
2237
  bg: 'canvas.default',
1843
- }, children: renderProtocolMessages() })) }), footerContent, showInput && renderProtocolInput(), showPoweredBy && _jsx(PoweredByTag, { ...poweredByProps })] }));
2238
+ }, children: renderProtocolMessages() })) }), footerContent, showInput && renderInputPrompt(), showPoweredBy && _jsx(PoweredByTag, { ...poweredByProps })] }));
1844
2239
  }
1845
2240
  export default ChatBase;