@datalayer/agent-runtimes 1.0.5 → 1.0.6

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 (111) hide show
  1. package/README.md +157 -10
  2. package/lib/AgentNode.d.ts +3 -0
  3. package/lib/AgentNode.js +676 -0
  4. package/lib/agent-node/themeStore.d.ts +3 -0
  5. package/lib/agent-node/themeStore.js +156 -0
  6. package/lib/agent-node-main.d.ts +1 -0
  7. package/lib/agent-node-main.js +14 -0
  8. package/lib/chat/Chat.js +16 -10
  9. package/lib/chat/ChatFloating.js +1 -1
  10. package/lib/chat/ChatSidebar.js +81 -49
  11. package/lib/chat/base/ChatBase.js +388 -74
  12. package/lib/chat/display/FloatingBrandButton.js +8 -1
  13. package/lib/chat/header/ChatHeader.d.ts +3 -1
  14. package/lib/chat/header/ChatHeader.js +15 -12
  15. package/lib/chat/header/ChatHeaderBase.d.ts +29 -9
  16. package/lib/chat/header/ChatHeaderBase.js +26 -3
  17. package/lib/chat/indicators/SandboxStatusIndicator.js +82 -47
  18. package/lib/chat/messages/ChatMessageList.js +46 -1
  19. package/lib/chat/messages/ChatMessages.js +6 -2
  20. package/lib/chat/prompt/InputFooter.d.ts +3 -1
  21. package/lib/chat/prompt/InputFooter.js +8 -5
  22. package/lib/chat/prompt/InputPrompt.d.ts +3 -1
  23. package/lib/chat/prompt/InputPrompt.js +2 -2
  24. package/lib/chat/prompt/InputPromptFooter.d.ts +3 -1
  25. package/lib/chat/prompt/InputPromptFooter.js +3 -3
  26. package/lib/client/AgentsMixin.js +14 -0
  27. package/lib/config/AgentConfiguration.d.ts +22 -0
  28. package/lib/config/AgentConfiguration.js +319 -64
  29. package/lib/examples/AgUiSharedStateExample.js +2 -1
  30. package/lib/examples/AgentCheckpointsExample.js +3 -3
  31. package/lib/examples/AgentCodemodeExample.d.ts +3 -3
  32. package/lib/examples/AgentCodemodeExample.js +24 -12
  33. package/lib/examples/AgentEvalsExample.js +330 -40
  34. package/lib/examples/AgentGuardrailsExample.js +16 -5
  35. package/lib/examples/AgentHooksExample.js +27 -9
  36. package/lib/examples/AgentInferenceProviderExample.d.ts +3 -0
  37. package/lib/examples/AgentInferenceProviderExample.js +329 -0
  38. package/lib/examples/AgentMCPExample.js +6 -5
  39. package/lib/examples/AgentMemoryExample.d.ts +1 -2
  40. package/lib/examples/AgentMemoryExample.js +71 -22
  41. package/lib/examples/AgentMonitoringExample.js +5 -5
  42. package/lib/examples/AgentNotificationsExample.d.ts +1 -2
  43. package/lib/examples/AgentNotificationsExample.js +71 -22
  44. package/lib/examples/AgentOtelExample.js +31 -40
  45. package/lib/examples/AgentOutputsExample.d.ts +1 -1
  46. package/lib/examples/AgentOutputsExample.js +67 -16
  47. package/lib/examples/AgentParametersExample.js +10 -8
  48. package/lib/examples/AgentSandboxExample.d.ts +1 -1
  49. package/lib/examples/AgentSandboxExample.js +7 -6
  50. package/lib/examples/AgentSkillsExample.js +6 -6
  51. package/lib/examples/AgentSubagentsExample.d.ts +1 -1
  52. package/lib/examples/AgentSubagentsExample.js +6 -6
  53. package/lib/examples/AgentToolApprovalsExample.js +27 -11
  54. package/lib/examples/AgentTriggersExample.js +5 -5
  55. package/lib/examples/{AgentSpecsExample.d.ts → AgentspecsExample.d.ts} +2 -2
  56. package/lib/examples/AgentspecsExample.js +1096 -0
  57. package/lib/examples/ChatCustomExample.js +6 -5
  58. package/lib/examples/ChatExample.js +6 -5
  59. package/lib/examples/Lexical2Example.js +1 -1
  60. package/lib/examples/LexicalAgentExample.js +1 -1
  61. package/lib/examples/NotebookAgentExample.js +3 -3
  62. package/lib/examples/components/ExampleWrapper.d.ts +6 -7
  63. package/lib/examples/components/ExampleWrapper.js +27 -10
  64. package/lib/examples/example-selector.js +2 -1
  65. package/lib/examples/index.d.ts +2 -1
  66. package/lib/examples/index.js +2 -1
  67. package/lib/examples/lexical/initial-content.json +6 -6
  68. package/lib/examples/main.js +56 -16
  69. package/lib/examples/utils/agentId.d.ts +1 -1
  70. package/lib/examples/utils/agentId.js +1 -1
  71. package/lib/examples/utils/useExampleAgentRuntimesUrl.d.ts +5 -0
  72. package/lib/examples/utils/useExampleAgentRuntimesUrl.js +19 -0
  73. package/lib/hooks/useAIAgentsWebSocket.js +35 -0
  74. package/lib/hooks/useAgentRuntimes.d.ts +32 -3
  75. package/lib/hooks/useAgentRuntimes.js +114 -19
  76. package/lib/index.d.ts +1 -1
  77. package/lib/specs/agents/agents.d.ts +20 -13
  78. package/lib/specs/agents/agents.js +1267 -581
  79. package/lib/specs/benchmarks.d.ts +20 -0
  80. package/lib/specs/benchmarks.js +205 -0
  81. package/lib/specs/envvars.d.ts +0 -1
  82. package/lib/specs/envvars.js +0 -11
  83. package/lib/specs/evals.d.ts +10 -9
  84. package/lib/specs/evals.js +128 -88
  85. package/lib/specs/index.d.ts +0 -1
  86. package/lib/specs/index.js +0 -1
  87. package/lib/specs/models.d.ts +0 -2
  88. package/lib/specs/models.js +0 -15
  89. package/lib/specs/skills.d.ts +0 -1
  90. package/lib/specs/skills.js +0 -18
  91. package/lib/stores/agentRuntimeStore.d.ts +5 -1
  92. package/lib/stores/agentRuntimeStore.js +22 -8
  93. package/lib/stores/conversationStore.js +2 -2
  94. package/lib/types/agents-lifecycle.d.ts +18 -0
  95. package/lib/types/agents.d.ts +6 -0
  96. package/lib/types/agentspecs.d.ts +4 -0
  97. package/lib/types/benchmarks.d.ts +43 -0
  98. package/lib/types/benchmarks.js +5 -0
  99. package/lib/types/chat.d.ts +16 -0
  100. package/lib/types/evals.d.ts +26 -17
  101. package/lib/types/index.d.ts +1 -0
  102. package/lib/types/index.js +1 -0
  103. package/package.json +9 -5
  104. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  105. package/scripts/codegen/__pycache__/generate_benchmarks.cpython-313.pyc +0 -0
  106. package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
  107. package/scripts/codegen/generate_agents.py +89 -43
  108. package/scripts/codegen/generate_benchmarks.py +441 -0
  109. package/scripts/codegen/generate_evals.py +94 -16
  110. package/scripts/codegen/generate_events.py +0 -1
  111. package/lib/examples/AgentSpecsExample.js +0 -694
@@ -0,0 +1,3 @@
1
+ import { type StoreApi, type UseBoundStore } from 'zustand';
2
+ import { type ThemeState } from '@datalayer/primer-addons';
3
+ export declare const useAgentNodeThemeStore: UseBoundStore<StoreApi<ThemeState>>;
@@ -0,0 +1,156 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { create } from 'zustand';
6
+ import { createJSONStorage, persist } from 'zustand/middleware';
7
+ import { themeConfigs, } from '@datalayer/primer-addons';
8
+ const STORAGE_KEY = 'agent-runtimes-agent-node-theme';
9
+ const COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;
10
+ const colorModeCycle = {
11
+ light: 'dark',
12
+ dark: 'auto',
13
+ auto: 'light',
14
+ };
15
+ const normalizeThemeVariant = (value) => {
16
+ // Backward compatibility: "ocean" maps to the Earth theme variant.
17
+ if (value === 'ocean') {
18
+ return 'earth';
19
+ }
20
+ if (value === 'datalayer' ||
21
+ value === 'spatial' ||
22
+ value === 'lovely' ||
23
+ value === 'matrix' ||
24
+ value === 'earth') {
25
+ return value;
26
+ }
27
+ return 'earth';
28
+ };
29
+ const cookieStorage = {
30
+ getItem: (name) => {
31
+ if (typeof document === 'undefined') {
32
+ return null;
33
+ }
34
+ const encodedName = `${encodeURIComponent(name)}=`;
35
+ const parts = document.cookie.split('; ');
36
+ for (const part of parts) {
37
+ if (part.startsWith(encodedName)) {
38
+ return decodeURIComponent(part.slice(encodedName.length));
39
+ }
40
+ }
41
+ return null;
42
+ },
43
+ setItem: (name, value) => {
44
+ if (typeof document === 'undefined') {
45
+ return;
46
+ }
47
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE_SECONDS}; samesite=lax`;
48
+ },
49
+ removeItem: (name) => {
50
+ if (typeof document === 'undefined') {
51
+ return;
52
+ }
53
+ document.cookie = `${encodeURIComponent(name)}=; path=/; max-age=0; samesite=lax`;
54
+ },
55
+ };
56
+ export const useAgentNodeThemeStore = create()(persist(set => ({
57
+ // "ocean dark" default: Earth theme (ocean palette) + dark mode.
58
+ colorMode: 'dark',
59
+ theme: 'earth',
60
+ activeVariant: null,
61
+ variants: {},
62
+ toggleColorMode: () => set(state => {
63
+ const next = colorModeCycle[state.colorMode];
64
+ if (state.activeVariant && state.variants[state.activeVariant]) {
65
+ return {
66
+ colorMode: next,
67
+ variants: {
68
+ ...state.variants,
69
+ [state.activeVariant]: {
70
+ ...state.variants[state.activeVariant],
71
+ colorMode: next,
72
+ },
73
+ },
74
+ };
75
+ }
76
+ return { colorMode: next };
77
+ }),
78
+ setColorMode: (mode) => set(state => {
79
+ if (state.activeVariant && state.variants[state.activeVariant]) {
80
+ return {
81
+ colorMode: mode,
82
+ variants: {
83
+ ...state.variants,
84
+ [state.activeVariant]: {
85
+ ...state.variants[state.activeVariant],
86
+ colorMode: mode,
87
+ },
88
+ },
89
+ };
90
+ }
91
+ return { colorMode: mode };
92
+ }),
93
+ setTheme: (theme, applyDefaultColorMode = true) => set(state => {
94
+ const normalizedTheme = normalizeThemeVariant(theme);
95
+ const nextColorMode = applyDefaultColorMode
96
+ ? themeConfigs[normalizedTheme].defaultColorMode
97
+ : state.colorMode;
98
+ if (state.activeVariant && state.variants[state.activeVariant]) {
99
+ return {
100
+ theme: normalizedTheme,
101
+ colorMode: nextColorMode,
102
+ variants: {
103
+ ...state.variants,
104
+ [state.activeVariant]: {
105
+ theme: normalizedTheme,
106
+ colorMode: nextColorMode,
107
+ },
108
+ },
109
+ };
110
+ }
111
+ return { theme: normalizedTheme, colorMode: nextColorMode };
112
+ }),
113
+ registerVariants: defs => set(state => {
114
+ const merged = { ...state.variants };
115
+ for (const [key, val] of Object.entries(defs)) {
116
+ if (!merged[key]) {
117
+ merged[key] = val;
118
+ }
119
+ }
120
+ return { variants: merged };
121
+ }),
122
+ setVariant: variant => set(state => {
123
+ const variantState = state.variants[variant];
124
+ if (!variantState) {
125
+ return { activeVariant: variant };
126
+ }
127
+ return {
128
+ activeVariant: variant,
129
+ theme: normalizeThemeVariant(variantState.theme),
130
+ colorMode: variantState.colorMode,
131
+ };
132
+ }),
133
+ }), {
134
+ name: STORAGE_KEY,
135
+ version: 1,
136
+ storage: createJSONStorage(() => cookieStorage),
137
+ partialize: state => ({
138
+ colorMode: state.colorMode,
139
+ theme: state.theme,
140
+ activeVariant: state.activeVariant,
141
+ variants: state.variants,
142
+ }),
143
+ merge: (persisted, current) => {
144
+ const persistedState = (persisted ?? {});
145
+ const persistedTheme = normalizeThemeVariant(persistedState.theme);
146
+ return {
147
+ ...current,
148
+ ...persistedState,
149
+ theme: persistedTheme,
150
+ variants: {
151
+ ...current.variants,
152
+ ...(persistedState.variants ?? {}),
153
+ },
154
+ };
155
+ },
156
+ }));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { createRoot } from 'react-dom/client';
7
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8
+ import { MemoryRouter } from 'react-router-dom';
9
+ import { AgentNode } from './AgentNode';
10
+ const queryClient = new QueryClient();
11
+ const rootElement = document.getElementById('root');
12
+ if (rootElement) {
13
+ createRoot(rootElement).render(_jsx(MemoryRouter, { initialEntries: ['/'], children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(AgentNode, {}) }) }));
14
+ }
package/lib/chat/Chat.js CHANGED
@@ -63,22 +63,25 @@ const queryClient = new QueryClient();
63
63
  * Get transport endpoint path
64
64
  */
65
65
  function getEndpointPath(protocol, agentId) {
66
+ const resolvedAgentId = typeof agentId === 'string' && agentId.trim().length > 0
67
+ ? agentId.trim()
68
+ : 'default';
66
69
  switch (protocol) {
67
70
  case 'vercel-ai':
68
- return `/api/v1/vercel-ai/${agentId}`;
71
+ return `/api/v1/vercel-ai/${resolvedAgentId}`;
69
72
  case 'vercel-ai-jupyter':
70
73
  // Jupyter server endpoint - same protocol as vercel-ai
71
74
  // Note: no leading slash - will be joined with baseUrl that may have trailing slash
72
75
  return 'agent_runtimes/chat';
73
76
  case 'ag-ui':
74
- return `/api/v1/ag-ui/${agentId}/`;
77
+ return `/api/v1/ag-ui/${resolvedAgentId}/`;
75
78
  case 'a2a':
76
79
  // A2A requires trailing slash for FastA2A compatibility
77
- return `/api/v1/a2a/agents/${agentId}/`;
80
+ return `/api/v1/a2a/agents/${resolvedAgentId}/`;
78
81
  case 'acp':
79
- return `/api/v1/acp/ws/${agentId}`;
82
+ return `/api/v1/acp/ws/${resolvedAgentId}`;
80
83
  default:
81
- return `/api/v1/agents/${agentId}/chat`;
84
+ return `/api/v1/agents/${resolvedAgentId}/chat`;
82
85
  }
83
86
  }
84
87
  /**
@@ -134,6 +137,9 @@ function getProtocolType(protocol) {
134
137
  * ```
135
138
  */
136
139
  export function Chat({ protocol: transport, extensions: _extensions, baseUrl = 'http://localhost:8765', wsUrl, agentId, authToken: authTokenProp, placeholder = 'Type your message...', title, subtitle, brandIcon, autoConnect: _autoConnect = true, streaming: _streaming = true, onMessageSent: _onMessageSent, onMessageReceived: _onMessageReceived, onDisconnect, onLogout: _onLogout, onCollapsePanel: _onCollapsePanel, className, height = '600px', showHeader = true, showNewChatButton = true, showClearButton = true, showModelSelector = true, showToolsMenu = true, showInput = true, disableInputPrompt = false, showSkillsMenu = true, codemodeEnabled = false, onToggleCodemode, showTokenUsage = true, initialModel, availableModels, mcpServers, initialSkills, clearOnMount: _clearOnMount = true, suggestions, submitOnSuggestionClick = true, description, headerContent, headerActions, autoFocus = false, identityProviders, onIdentityConnect, onIdentityDisconnect, runtimeId, historyEndpoint, pendingPrompt, errorBanner, showInformation = true, chatViewMode, onChatViewModeChange, frontendTools, onToolCallStart, onToolCallComplete, renderToolResult, hideMessagesAfterToolUI = false, contextSnapshot, mcpStatusData, codemodeStatusData, sandboxStatusData, showToolApprovalBanner, pendingApprovals, onApproveApproval, onRejectApproval, }) {
140
+ const resolvedAgentId = typeof agentId === 'string' && agentId.trim().length > 0
141
+ ? agentId.trim()
142
+ : 'default';
137
143
  const [error, setError] = useState(null);
138
144
  const [isInitializing, setIsInitializing] = useState(true);
139
145
  const [showDetails, setShowDetails] = useState(false);
@@ -201,21 +207,21 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
201
207
  break;
202
208
  }
203
209
  case 'acp': {
204
- endpoint = `${baseUrl}${getEndpointPath(transport, agentId)}`;
210
+ endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
205
211
  const acpWsUrl = wsUrl ||
206
- `${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${agentId}`;
212
+ `${baseUrl.replace('http', 'ws')}/api/v1/acp/ws/${resolvedAgentId}`;
207
213
  options = { wsUrl: acpWsUrl };
208
214
  break;
209
215
  }
210
216
  default: {
211
- endpoint = `${baseUrl}${getEndpointPath(transport, agentId)}`;
217
+ endpoint = `${baseUrl}${getEndpointPath(transport, resolvedAgentId)}`;
212
218
  break;
213
219
  }
214
220
  }
215
221
  return {
216
222
  type: getProtocolType(transport),
217
223
  endpoint,
218
- agentId,
224
+ agentId: resolvedAgentId,
219
225
  authToken,
220
226
  options,
221
227
  // Enable config query for all protocols to fetch models and tools
@@ -232,7 +238,7 @@ export function Chat({ protocol: transport, extensions: _extensions, baseUrl = '
232
238
  setError(err instanceof Error ? err.message : 'Failed to configure');
233
239
  return undefined;
234
240
  }
235
- }, [transport, baseUrl, wsUrl, agentId, authTokenProp]);
241
+ }, [transport, baseUrl, wsUrl, resolvedAgentId, authTokenProp]);
236
242
  // Set initialized once protocol config is built
237
243
  useEffect(() => {
238
244
  if (protocolConfig) {
@@ -431,7 +431,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
431
431
  }, children: _jsx(ChatBase, { title: title, showHeader: showHeader, useStore: useStoreMode, protocol: protocol, autoFocus: isOpen, focusTrigger: focusTrigger, brandIcon: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }), headerButtons: {
432
432
  showNewChat: showNewChatButton,
433
433
  showClear: showClearButton && messages.length > 0,
434
- showSettings: showSettingsButton,
434
+ showSettings: showSettingsButton && !!onSettingsClick,
435
435
  onNewChat: handleNewChat,
436
436
  onClear: handleClear,
437
437
  onSettings: onSettingsClick,
@@ -12,10 +12,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
12
12
  * @module chat/ChatSidebar
13
13
  */
14
14
  import { useCallback, useEffect, useRef, useState } from 'react';
15
- import { IconButton, Text } from '@primer/react';
15
+ import { createPortal } from 'react-dom';
16
+ import { IconButton } from '@primer/react';
16
17
  import { Box } from '@datalayer/primer-addons';
17
18
  import { SidebarCollapseIcon, SidebarExpandIcon, XIcon, } from '@primer/octicons-react';
18
- import { AiAgentIcon } from '@datalayer/icons-react';
19
19
  import { useChatKeyboardShortcuts, getShortcutDisplay, } from '@datalayer/core/lib/hooks';
20
20
  import { ChatBase } from './base/ChatBase';
21
21
  import { useChatStore, useChatOpen, useChatMessages, } from '../stores/chatStore';
@@ -45,6 +45,37 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
45
45
  const sidebarRef = useRef(null);
46
46
  const inputRef = useRef(null);
47
47
  const isMobile = useIsMobile();
48
+ const [desktopViewportHeight, setDesktopViewportHeight] = useState(null);
49
+ // Compute available desktop height from the element's top position so the
50
+ // sidebar always fits within the visible viewport.
51
+ useEffect(() => {
52
+ if (isMobile) {
53
+ setDesktopViewportHeight(null);
54
+ return;
55
+ }
56
+ const updateHeight = () => {
57
+ const el = sidebarRef.current;
58
+ if (!el)
59
+ return;
60
+ const rect = el.getBoundingClientRect();
61
+ const viewportHeight = window.visualViewport?.height || window.innerHeight || 0;
62
+ const bottomPadding = 4; // keep a small breathing space from viewport edge
63
+ const available = Math.max(220, Math.floor(viewportHeight - rect.top - bottomPadding));
64
+ setDesktopViewportHeight(prev => prev !== null && Math.abs(prev - available) < 2 ? prev : available);
65
+ };
66
+ updateHeight();
67
+ window.addEventListener('resize', updateHeight);
68
+ window.addEventListener('scroll', updateHeight, true);
69
+ const observer = new ResizeObserver(() => updateHeight());
70
+ if (sidebarRef.current) {
71
+ observer.observe(sidebarRef.current);
72
+ }
73
+ return () => {
74
+ window.removeEventListener('resize', updateHeight);
75
+ window.removeEventListener('scroll', updateHeight, true);
76
+ observer.disconnect();
77
+ };
78
+ }, [isMobile]);
48
79
  // Initialize open state from defaultOpen
49
80
  useEffect(() => {
50
81
  setOpen(defaultOpen);
@@ -137,58 +168,45 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
137
168
  : SidebarExpandIcon, "aria-label": `Close sidebar${shortcutHint ? ` (${shortcutHint})` : ''}`, onClick: handleToggle, variant: "invisible", size: "small" }));
138
169
  // Collapsed state
139
170
  if (!isOpen) {
140
- return (_jsxs(Box, { ref: sidebarRef, className: className, sx: {
171
+ const collapsedLauncher = (_jsx(Box, { ref: sidebarRef, className: className, sx: {
141
172
  position: 'fixed',
142
173
  top: 12,
143
- ...(position === 'right' ? { right: 12 } : { left: 12 }),
174
+ ...(position === 'right'
175
+ ? { right: 'env(safe-area-inset-right)' }
176
+ : { left: 'env(safe-area-inset-left)' }),
144
177
  zIndex: 1001,
145
178
  display: 'flex',
146
179
  flexDirection: 'column',
147
180
  alignItems: 'center',
148
181
  gap: 2,
149
- }, children: [_jsxs(Box, { sx: { position: 'relative' }, children: [_jsx(IconButton, { icon: position === 'right' ? SidebarExpandIcon : SidebarCollapseIcon, "aria-label": `Open chat${shortcutHint ? ` (${shortcutHint})` : ''}`, onClick: handleToggle, variant: "default", size: "small", sx: {
150
- bg: 'canvas.default',
151
- border: '1px solid',
152
- borderColor: 'border.default',
153
- boxShadow: 'shadow.small',
154
- } }), messages.length > 0 && (_jsx(Box, { sx: {
155
- position: 'absolute',
156
- top: -6,
157
- right: -6,
158
- minWidth: 16,
159
- height: 16,
160
- px: 1,
161
- display: 'flex',
162
- alignItems: 'center',
163
- justifyContent: 'center',
164
- bg: 'accent.emphasis',
165
- color: 'fg.onEmphasis',
166
- borderRadius: '50%',
167
- fontSize: '10px',
168
- fontWeight: 'bold',
169
- boxShadow: 'shadow.small',
170
- }, children: messages.length > 99 ? '99+' : messages.length }))] }), _jsx(Box, { sx: {
171
- width: 32,
172
- height: 32,
173
- display: 'flex',
174
- alignItems: 'center',
175
- justifyContent: 'center',
176
- bg: 'canvas.default',
177
- border: '1px solid',
178
- borderColor: 'border.default',
179
- borderRadius: 2,
180
- boxShadow: 'shadow.small',
181
- }, children: brandIcon || _jsx(AiAgentIcon, { colored: true, size: 20 }) }), shortcutHint && (_jsx(Box, { sx: {
182
- px: 1,
183
- py: '2px',
184
- bg: 'canvas.default',
185
- border: '1px solid',
186
- borderColor: 'border.default',
187
- color: 'fg.muted',
188
- borderRadius: 1,
189
- fontSize: 0,
190
- boxShadow: 'shadow.small',
191
- }, children: _jsx(Text, { sx: { fontSize: '10px', fontFamily: 'mono' }, children: shortcutHint }) }))] }));
182
+ }, children: _jsxs(Box, { sx: { position: 'relative' }, children: [_jsx(IconButton, { icon: position === 'right' ? SidebarExpandIcon : SidebarCollapseIcon, "aria-label": "Open chat", description: shortcutHint ? `Open chat (${shortcutHint})` : 'Open chat', onClick: handleToggle, variant: "default", size: "small", sx: {
183
+ bg: 'canvas.default',
184
+ border: '1px solid',
185
+ borderColor: 'border.default',
186
+ boxShadow: 'shadow.small',
187
+ } }), messages.length > 0 && (_jsx(Box, { sx: {
188
+ position: 'absolute',
189
+ top: -6,
190
+ right: -6,
191
+ minWidth: 16,
192
+ height: 16,
193
+ px: 1,
194
+ display: 'flex',
195
+ alignItems: 'center',
196
+ justifyContent: 'center',
197
+ bg: 'accent.emphasis',
198
+ color: 'fg.onEmphasis',
199
+ borderRadius: '50%',
200
+ fontSize: '10px',
201
+ fontWeight: 'bold',
202
+ boxShadow: 'shadow.small',
203
+ }, children: messages.length > 99 ? '99+' : messages.length }))] }) }));
204
+ // Render the collapsed launcher in a body portal so it stays pinned to the
205
+ // viewport edge even when ancestors use transforms/positioning contexts.
206
+ if (typeof document !== 'undefined' && document.body) {
207
+ return createPortal(collapsedLauncher, document.body);
208
+ }
209
+ return collapsedLauncher;
192
210
  }
193
211
  // Mobile full-screen overlay
194
212
  const mobileStyles = isMobile
@@ -217,21 +235,35 @@ export function ChatSidebar({ title = 'Chat', protocol: protocolProp, defaultOpe
217
235
  position: 'relative',
218
236
  display: 'flex',
219
237
  flexDirection: 'column',
238
+ alignSelf: 'stretch',
220
239
  width: isMobile
221
240
  ? '100%'
222
241
  : typeof width === 'number'
223
242
  ? `${width}px`
224
243
  : width,
225
- height: '100%',
244
+ height: isMobile
245
+ ? '100%'
246
+ : desktopViewportHeight
247
+ ? `${desktopViewportHeight}px`
248
+ : 'calc(100dvh - 8px)',
249
+ minHeight: 0,
250
+ maxHeight: isMobile
251
+ ? '100%'
252
+ : desktopViewportHeight
253
+ ? `${desktopViewportHeight}px`
254
+ : 'calc(100dvh - 8px)',
255
+ marginBlock: isMobile ? 0 : '4px',
256
+ flex: isMobile ? '1 1 auto' : '0 0 auto',
226
257
  bg: 'canvas.default',
227
258
  borderLeft: !isMobile && position === 'right' ? '1px solid' : 'none',
228
259
  borderRight: !isMobile && position === 'left' ? '1px solid' : 'none',
229
260
  borderColor: 'border.default',
261
+ overflow: 'hidden',
230
262
  ...mobileStyles,
231
263
  }, children: _jsx(ChatBase, { title: title, showHeader: showHeader, brandIcon: brandIcon, protocol: protocolProp, headerButtons: {
232
264
  showNewChat: showNewChatButton,
233
265
  showClear: showClearButton && messages.length > 0,
234
- showSettings: showSettingsButton,
266
+ showSettings: showSettingsButton && !!onSettingsClick,
235
267
  onNewChat: handleNewChat,
236
268
  onClear: handleClear,
237
269
  onSettings: onSettingsClick,