@datalayer/agent-runtimes 1.3.74 → 1.3.78

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 (143) hide show
  1. package/lib/agents/AgentDetails.d.ts +16 -2
  2. package/lib/agents/AgentDetails.js +381 -272
  3. package/lib/chat/ChatFloating.d.ts +27 -1
  4. package/lib/chat/ChatFloating.js +97 -13
  5. package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
  6. package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
  7. package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
  8. package/lib/chat/assistant/AssistantContextMenu.js +68 -0
  9. package/lib/chat/assistant/AssistantStage.d.ts +144 -4
  10. package/lib/chat/assistant/AssistantStage.js +280 -10
  11. package/lib/chat/assistant/BalloonParts.d.ts +33 -5
  12. package/lib/chat/assistant/BalloonParts.js +66 -44
  13. package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
  14. package/lib/chat/assistant/BalloonVisual.js +4 -3
  15. package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
  16. package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
  17. package/lib/chat/assistant/SpeechBalloon.d.ts +54 -6
  18. package/lib/chat/assistant/SpeechBalloon.js +73 -22
  19. package/lib/chat/assistant/assistantDetails.d.ts +84 -0
  20. package/lib/chat/assistant/assistantDetails.js +140 -0
  21. package/lib/chat/assistant/formats/files.d.ts +23 -0
  22. package/lib/chat/assistant/formats/files.js +48 -0
  23. package/lib/chat/assistant/formats/index.d.ts +2 -0
  24. package/lib/chat/assistant/formats/index.js +1 -0
  25. package/lib/chat/assistant/state.d.ts +7 -14
  26. package/lib/chat/assistant/state.js +21 -20
  27. package/lib/chat/assistant/toolLine.d.ts +16 -0
  28. package/lib/chat/assistant/toolLine.js +46 -0
  29. package/lib/chat/base/ChatBase.js +71 -1
  30. package/lib/chat/base/loopMessage.d.ts +37 -0
  31. package/lib/chat/base/loopMessage.js +37 -0
  32. package/lib/chat/indicators/TypingDots.d.ts +13 -0
  33. package/lib/chat/indicators/TypingDots.js +21 -0
  34. package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
  35. package/lib/chat/messages/ChatMarkdown.js +18 -0
  36. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  37. package/lib/chat/messages/ChatMessageList.js +25 -33
  38. package/lib/chat/styles/streamdownStyles.js +3 -1
  39. package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
  40. package/lib/chat/tools/ToolCallDisplay.js +8 -7
  41. package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
  42. package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
  43. package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
  44. package/lib/components/inspector/AgentInspector.d.ts +59 -0
  45. package/lib/components/inspector/AgentInspector.js +220 -0
  46. package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
  47. package/lib/components/inspector/AgentInspectorDialog.js +9 -0
  48. package/lib/components/inspector/JsonTree.d.ts +24 -0
  49. package/lib/components/inspector/JsonTree.js +89 -0
  50. package/lib/components/inspector/a2aInspect.d.ts +56 -0
  51. package/lib/components/inspector/a2aInspect.js +478 -0
  52. package/lib/components/inspector/agentInspector.d.ts +179 -0
  53. package/lib/components/inspector/agentInspector.js +267 -0
  54. package/lib/components/inspector/chatInspect.d.ts +38 -0
  55. package/lib/components/inspector/chatInspect.js +65 -0
  56. package/lib/components/inspector/classify.d.ts +19 -0
  57. package/lib/components/inspector/classify.js +42 -0
  58. package/lib/components/inspector/index.d.ts +14 -0
  59. package/lib/components/inspector/index.js +18 -0
  60. package/lib/components/teams/A2ATeamGraph.d.ts +69 -1
  61. package/lib/components/teams/A2ATeamGraph.js +277 -37
  62. package/lib/components/teams/NotebookPreviewView.js +9 -1
  63. package/lib/components/teams/TeamNotebook.d.ts +3 -1
  64. package/lib/components/teams/TeamNotebook.js +3 -3
  65. package/lib/components/teams/TeamNotebookView.js +15 -0
  66. package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
  67. package/lib/components/teams/jupyterThemeVariables.js +441 -0
  68. package/lib/components/teams/teamConnections.d.ts +9 -1
  69. package/lib/components/teams/teamConnections.js +20 -0
  70. package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
  71. package/lib/components/teams/teamNotebookKernel.js +17 -0
  72. package/lib/components/teams/useA2ATeam.d.ts +20 -3
  73. package/lib/components/teams/useA2ATeam.js +108 -23
  74. package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
  75. package/lib/examples/AgentA2ATeamExample.js +67 -13
  76. package/lib/examples/AssistantExample.js +65 -26
  77. package/lib/examples/AssistantGalleryExample.js +2 -1
  78. package/lib/examples/exampleGroups.d.ts +1 -1
  79. package/lib/examples/exampleGroups.js +1 -1
  80. package/lib/examples/utils/assistantGallery.d.ts +7 -6
  81. package/lib/examples/utils/assistantGallery.js +23 -8
  82. package/lib/jupyter/exec/Python.d.ts +0 -1
  83. package/lib/jupyter/exec/Python.js +0 -32
  84. package/lib/jupyter/exec/Snippets.d.ts +4 -12
  85. package/lib/jupyter/exec/Snippets.js +0 -8
  86. package/lib/loop/apps/AppFeedback.js +3 -1
  87. package/lib/loop/apps/AppKept.d.ts +22 -0
  88. package/lib/loop/apps/AppKept.js +36 -0
  89. package/lib/loop/apps/AppRenderer.d.ts +11 -1
  90. package/lib/loop/apps/AppRenderer.js +22 -4
  91. package/lib/loop/apps/checks.d.ts +7 -0
  92. package/lib/loop/apps/checks.js +39 -0
  93. package/lib/loop/apps/index.d.ts +3 -0
  94. package/lib/loop/apps/index.js +3 -0
  95. package/lib/loop/apps/kept.d.ts +30 -0
  96. package/lib/loop/apps/kept.js +52 -0
  97. package/lib/loop/apps/records.d.ts +12 -4
  98. package/lib/loop/apps/records.js +13 -5
  99. package/lib/loop/apps/saved.d.ts +30 -0
  100. package/lib/loop/apps/saved.js +40 -0
  101. package/lib/loop/core/index.d.ts +17 -0
  102. package/lib/loop/core/index.js +1 -0
  103. package/lib/loop/embed/AppEmbed.d.ts +8 -1
  104. package/lib/loop/embed/AppEmbed.js +4 -4
  105. package/lib/loop/embed/LoopEmbed.d.ts +6 -1
  106. package/lib/loop/embed/LoopEmbed.js +4 -2
  107. package/lib/loop/embed/element.js +1 -1
  108. package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
  109. package/lib/loop/plugins/agents/browserService.d.ts +7 -1
  110. package/lib/loop/plugins/agents/browserService.js +4 -0
  111. package/lib/loop/plugins/agents/plugin.d.ts +16 -0
  112. package/lib/loop/plugins/agents/switchable.d.ts +3 -0
  113. package/lib/loop/plugins/agents/switchable.js +1 -0
  114. package/lib/loop/plugins/app-activity/index.js +3 -0
  115. package/lib/loop/plugins/app-computer/index.js +3 -0
  116. package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
  117. package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
  118. package/lib/loop/plugins/app-rules/index.js +3 -0
  119. package/lib/loop/plugins/assistant/index.d.ts +7 -2
  120. package/lib/loop/plugins/assistant/index.js +58 -4
  121. package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
  122. package/lib/loop/plugins/assistant-characters/index.js +17 -0
  123. package/lib/loop/plugins/chat/ChatView.js +4 -1
  124. package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
  125. package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
  126. package/lib/loop/presets.d.ts +4 -0
  127. package/lib/loop/presets.js +3 -1
  128. package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
  129. package/lib/loop/shell/LoopWorkspace.js +5 -4
  130. package/lib/loop/shell/SidebarRail.d.ts +53 -0
  131. package/lib/loop/shell/SidebarRail.js +74 -0
  132. package/lib/loop/shell/index.d.ts +2 -0
  133. package/lib/loop/shell/index.js +2 -0
  134. package/lib/loop/shell/paneMotion.d.ts +21 -0
  135. package/lib/loop/shell/paneMotion.js +37 -0
  136. package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
  137. package/lib/specs/apps.js +8 -5
  138. package/lib/specs/uiPlugins.js +529 -0
  139. package/lib/types/agentspecs.d.ts +5 -3
  140. package/lib/types/chat.d.ts +12 -0
  141. package/package.json +2 -2
  142. package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
  143. package/scripts/codegen/generate_ui_plugins.py +112 -2
@@ -0,0 +1,214 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { Button, Dialog, Heading, IconButton, Label, Link, Text, } from '@primer/react';
4
+ import { CheckIcon, CopyIcon } from '@primer/octicons-react';
5
+ import { Box } from '@datalayer/primer-addons';
6
+ import { KernelAPI, KernelConnection, ServerConnection, } from '@jupyterlab/services';
7
+ import { KernelVariables, executeSilently, } from '@datalayer/jupyter-react';
8
+ import { NO_SANDBOX_YET, SANDBOX_KIND_LABELS, displayUrl, sandboxIsLive, sandboxExecuteOverHttp, timeLeft, } from './assistantDetails';
9
+ /** A status, as a Primer label. */
10
+ function StatusLabel({ status }) {
11
+ const variant = status === 'running' || status === 'idle'
12
+ ? 'success'
13
+ : status === 'busy' || status === 'starting'
14
+ ? 'attention'
15
+ : status === 'error' || status === 'dead'
16
+ ? 'danger'
17
+ : 'secondary';
18
+ return (_jsx(Label, { variant: variant, size: "small", "data-sandbox-status": status, children: status }));
19
+ }
20
+ /** A URL, linked, with a copy button. */
21
+ function UrlValue({ url }) {
22
+ const [copied, setCopied] = useState(false);
23
+ return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, minWidth: 0 }, children: [_jsx(Link, { href: url, target: "_blank", rel: "noreferrer", sx: { fontFamily: 'mono', fontSize: 0, wordBreak: 'break-all' }, children: url }), _jsx(IconButton, { icon: copied ? CheckIcon : CopyIcon, "aria-label": "Copy the URL", size: "small", variant: "invisible", onClick: () => {
24
+ void navigator.clipboard?.writeText(url).then(() => {
25
+ setCopied(true);
26
+ setTimeout(() => setCopied(false), 1500);
27
+ });
28
+ } })] }));
29
+ }
30
+ /** A moment, for a reader. */
31
+ function moment(value) {
32
+ if (value === undefined || value === '') {
33
+ return undefined;
34
+ }
35
+ const at = typeof value === 'number'
36
+ ? new Date(value < 1e12 ? value * 1000 : value)
37
+ : new Date(value);
38
+ return Number.isNaN(at.getTime()) ? undefined : at.toLocaleString();
39
+ }
40
+ const PYODIDE_MARKER = '__DL_PYODIDE__';
41
+ const PYODIDE_SNIPPET = `def __dl_pyodide():
42
+ import json, sys
43
+ facts = {'python': sys.version.split()[0], 'packages': []}
44
+ try:
45
+ import pyodide
46
+ facts['version'] = pyodide.__version__
47
+ except Exception:
48
+ pass
49
+ try:
50
+ import pyodide_js
51
+ facts['packages'] = sorted(str(k) for k in pyodide_js.loadedPackages.object_keys())
52
+ except Exception:
53
+ pass
54
+ print('${PYODIDE_MARKER}' + json.dumps(facts) + '${PYODIDE_MARKER}')
55
+ try:
56
+ __dl_pyodide()
57
+ finally:
58
+ del __dl_pyodide
59
+ `;
60
+ /** Pyodide's facts out of what the snippet printed. */
61
+ export function parsePyodideFacts(stdout) {
62
+ const end = stdout.lastIndexOf(PYODIDE_MARKER);
63
+ const start = end > 0 ? stdout.lastIndexOf(PYODIDE_MARKER, end - 1) : -1;
64
+ if (start < 0) {
65
+ return undefined;
66
+ }
67
+ try {
68
+ const facts = JSON.parse(stdout.slice(start + PYODIDE_MARKER.length, end));
69
+ return {
70
+ version: facts.version,
71
+ python: facts.python,
72
+ packages: Array.isArray(facts.packages) ? facts.packages : [],
73
+ };
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ }
79
+ /** The kernel connection the dialog lists from: the host's, or one opened here. */
80
+ function useKernelConnection(sandbox) {
81
+ const [opened, setOpened] = useState(null);
82
+ const { connection, url, token, kernelId, kind } = sandbox;
83
+ const live = sandboxIsLive(sandbox);
84
+ useEffect(() => {
85
+ if (connection || !url || !kernelId || kind === 'browser' || !live) {
86
+ return;
87
+ }
88
+ const base = url.replace(/\/+$/, '') + '/';
89
+ const serverSettings = ServerConnection.makeSettings({
90
+ baseUrl: base,
91
+ wsUrl: base.replace(/^http/, 'ws'),
92
+ token: token ?? '',
93
+ appendToken: true,
94
+ });
95
+ const opening = new KernelConnection({
96
+ model: { id: kernelId, name: '' },
97
+ serverSettings,
98
+ handleComms: true,
99
+ });
100
+ setOpened(opening);
101
+ return () => {
102
+ setOpened(null);
103
+ opening.dispose();
104
+ };
105
+ }, [connection, url, token, kernelId, kind, live]);
106
+ return live ? (connection ?? opened) : null;
107
+ }
108
+ export function SandboxDetailsDialog({ sandbox, name, onClose, }) {
109
+ const connection = useKernelConnection(sandbox);
110
+ const [kernel, setKernel] = useState({});
111
+ const [pyodide, setPyodide] = useState();
112
+ const [showPackages, setShowPackages] = useState(false);
113
+ // The kernel, as it says it is: its name, language, status, last activity.
114
+ useEffect(() => {
115
+ if (!connection) {
116
+ setKernel({});
117
+ return;
118
+ }
119
+ let cancelled = false;
120
+ const update = (next) => !cancelled && setKernel(current => ({ ...current, ...next }));
121
+ update({
122
+ id: connection.id,
123
+ name: connection.name || undefined,
124
+ status: connection.status,
125
+ });
126
+ connection.info
127
+ .then(info => update({
128
+ language: [info.language_info?.name, info.language_info?.version]
129
+ .filter(Boolean)
130
+ .join(' '),
131
+ }))
132
+ .catch(() => undefined);
133
+ if (sandbox.kind !== 'browser') {
134
+ KernelAPI.getKernelModel(connection.id, connection.serverSettings)
135
+ .then(model => update({
136
+ name: model?.name || undefined,
137
+ lastActivity: model?.last_activity,
138
+ }))
139
+ .catch(() => undefined);
140
+ }
141
+ const onStatus = (_, status) => update({ status });
142
+ connection.statusChanged.connect(onStatus);
143
+ return () => {
144
+ cancelled = true;
145
+ connection.statusChanged.disconnect(onStatus);
146
+ };
147
+ }, [connection, sandbox.kind]);
148
+ // Pyodide's version and packages, asked once of the browser kernel.
149
+ useEffect(() => {
150
+ if (sandbox.kind !== 'browser' || !connection) {
151
+ return;
152
+ }
153
+ let cancelled = false;
154
+ executeSilently(connection, PYODIDE_SNIPPET)
155
+ .then(({ stdout }) => {
156
+ if (!cancelled) {
157
+ setPyodide(parsePyodideFacts(stdout));
158
+ }
159
+ })
160
+ .catch(() => undefined);
161
+ return () => {
162
+ cancelled = true;
163
+ };
164
+ }, [connection, sandbox.kind]);
165
+ // Without a connection, the agent's server runs the snippet in its sandbox.
166
+ const execute = useMemo(() =>
167
+ // Not before the sandbox is up: the server would start one to answer.
168
+ !connection && sandbox.serverUrl && sandboxIsLive(sandbox)
169
+ ? sandboxExecuteOverHttp(sandbox.serverUrl, sandbox.agentId)
170
+ : undefined, [connection, sandbox.serverUrl, sandbox.agentId]);
171
+ const url = displayUrl(sandbox.url ?? sandbox.serverUrl);
172
+ const left = timeLeft(sandbox.expiresAt);
173
+ const facts = [];
174
+ const add = (key, value) => {
175
+ if (value !== undefined && value !== null && value !== '') {
176
+ facts.push([key, value]);
177
+ }
178
+ };
179
+ add('Environment', sandbox.environment);
180
+ add('URL', url ? _jsx(UrlValue, { url: url }) : undefined);
181
+ add('Kernel', kernel.name || kernel.id ? (_jsx(Text, { sx: { fontFamily: 'mono', fontSize: 0 }, children: [kernel.name, kernel.id?.slice(0, 8)].filter(Boolean).join(' · ') })) : undefined);
182
+ add('Language', kernel.language);
183
+ add('Kernel status', kernel.status ? _jsx(StatusLabel, { status: kernel.status }) : undefined);
184
+ add('Agent', sandbox.kind === 'local' || sandbox.kind === 'runtime'
185
+ ? sandbox.agentId
186
+ : undefined);
187
+ add('Runtime', sandbox.runtimeUid);
188
+ add('Time left', left);
189
+ add('Burning rate', sandbox.burningRate !== undefined
190
+ ? `${sandbox.burningRate} credits/h`
191
+ : undefined);
192
+ add('Pyodide', pyodide?.version);
193
+ add('Packages', pyodide && pyodide.packages.length > 0 ? (_jsxs(Box, { children: [_jsx(Button, { size: "small", variant: "invisible", onClick: () => setShowPackages(!showPackages), "aria-expanded": showPackages, sx: { px: 1 }, children: `${pyodide.packages.length} loaded` }), showPackages && (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, fontFamily: 'mono', fontSize: 0 }, "data-sandbox-packages": "", children: pyodide.packages.join(', ') }))] })) : undefined);
194
+ add('Started', moment(sandbox.startedAt));
195
+ add('Last activity', moment(sandbox.lastActivity ?? kernel.lastActivity));
196
+ return (_jsx(Dialog, { title: `${name} · Code Sandbox`, onClose: onClose, width: "xlarge", height: "large", children: _jsxs(Box, { "data-assistant-sandbox-details": "", sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [_jsxs(Box, { "data-sandbox-summary": "", sx: {
197
+ p: 3,
198
+ bg: 'canvas.subtle',
199
+ border: '1px solid',
200
+ borderColor: 'border.default',
201
+ borderRadius: 2,
202
+ }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsxs(Text, { sx: { fontWeight: 'semibold' }, "data-sandbox-kind": sandbox.kind, children: [SANDBOX_KIND_LABELS[sandbox.kind], sandbox.kind === 'runtime' && sandbox.variant
203
+ ? ` (${sandbox.variant})`
204
+ : ''] }), sandbox.status && _jsx(StatusLabel, { status: sandbox.status })] }), _jsx(Box, { as: "dl", sx: {
205
+ display: 'grid',
206
+ gridTemplateColumns: 'max-content minmax(0, 1fr)',
207
+ columnGap: 3,
208
+ rowGap: 1,
209
+ m: 0,
210
+ fontSize: 1,
211
+ alignItems: 'center',
212
+ }, children: facts.map(([key, value]) => (_jsxs(Box, { sx: { display: 'contents' }, children: [_jsx(Box, { as: "dt", sx: { color: 'fg.muted' }, children: key }), _jsx(Box, { as: "dd", sx: { m: 0, minWidth: 0 }, "data-sandbox-fact": key, children: value })] }, key))) })] }), _jsxs(Box, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: "Variables" }), sandboxIsLive(sandbox) ? (_jsx(KernelVariables, { connection: connection, execute: execute, maxHeight: "calc(80vh - 360px)" })) : (_jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, "data-sandbox-not-running": "", children: NO_SANDBOX_YET }))] })] }) }));
213
+ }
214
+ export default SandboxDetailsDialog;
@@ -13,6 +13,9 @@
13
13
  * them all — the person's and the agent's, under a header that counts them,
14
14
  * scrolled to the newest — rather than the newest line alone.
15
15
  *
16
+ * Given suggestions while the agent waits for a question, it offers them as
17
+ * chips under its words: one clicked is sent as the person's prompt.
18
+ *
16
19
  * Shown `current` (LOOP T-23), it is the one thing being said or done now:
17
20
  * *Now*, then the words as they are written, or the tool being called
18
21
  * ("Using **list_invoices**…"), cut to a few lines, and what goes with them
@@ -23,9 +26,20 @@
23
26
  * @module chat/assistant/SpeechBalloon
24
27
  */
25
28
  import type { JSX, ReactNode } from 'react';
26
- import { type BalloonApproval, type BalloonHistoryMessage } from './state';
29
+ import { type BalloonApproval } from './state';
30
+ import type { DisplayItem } from '../../types/chat';
27
31
  import type { DecisionAsker } from './decisions';
28
- import type { BalloonDisplay, BalloonToolLine } from './toolLine';
32
+ import { type BalloonDisplay, type BalloonToolLine } from './toolLine';
33
+ /** A suggestion the balloon offers: its label, and the prompt it sends. */
34
+ export type BalloonSuggestion = {
35
+ label: string;
36
+ prompt: string;
37
+ };
38
+ /** The suggestions, as chips: each sends its prompt. */
39
+ export declare function BalloonSuggestions({ suggestions, onSuggestion, }: {
40
+ suggestions: readonly BalloonSuggestion[];
41
+ onSuggestion: (suggestion: BalloonSuggestion) => void;
42
+ }): JSX.Element;
29
43
  export interface SpeechBalloonProps {
30
44
  text: string;
31
45
  /** There is more than the balloon holds. */
@@ -64,10 +78,11 @@ export interface SpeechBalloonProps {
64
78
  /** What goes with the words, under them: a notebook given, read-only. */
65
79
  attachment?: ReactNode;
66
80
  /**
67
- * The conversation's messages (`history`): listed in the balloon, every
68
- * one, in place of the newest line alone.
81
+ * The conversation (`history`), in the chat's own model — its messages
82
+ * and tool calls, as the chat holds them — listed in the balloon by the
83
+ * chat's own components, in place of the newest line alone.
69
84
  */
70
- history?: readonly BalloonHistoryMessage[];
85
+ history?: readonly DisplayItem[];
71
86
  /**
72
87
  * What goes with the words is a large visual (a notebook): an *Expand*
73
88
  * button under it draws it large, named by `expandTitle`.
@@ -75,10 +90,43 @@ export interface SpeechBalloonProps {
75
90
  onExpand?: () => void;
76
91
  /** What the large visual is, for the *Expand* button: `Accounting's notebook`. */
77
92
  expandTitle?: string;
93
+ /** Draws the listed history large (`history` display): its *Expand*. */
94
+ onExpandHistory?: () => void;
95
+ /**
96
+ * What the person may ask, as chips under the words: one clicked is sent
97
+ * as the prompt (`onSuggestion`), as if typed and asked.
98
+ */
99
+ suggestions?: readonly BalloonSuggestion[];
100
+ onSuggestion?: (suggestion: BalloonSuggestion) => void;
101
+ /**
102
+ * At work with nothing written yet: the chat's three dots — after the
103
+ * current line, or last in the history — until words arrive.
104
+ */
105
+ waiting?: boolean;
106
+ /**
107
+ * The words uncut, when `text` was cut to fit: *more* shows them whole,
108
+ * in the balloon, *less* folds them back.
109
+ */
110
+ fullText?: string;
78
111
  }
79
112
  /** How tall the listed conversation grows before it scrolls, in pixels. */
80
113
  export declare const BALLOON_HISTORY_MAX_HEIGHT = 220;
81
114
  /** Shorter, when something goes with the words under it (a notebook). */
82
115
  export declare const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
83
- export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, display, tool, speaking, busy, attachment, history, onExpand, expandTitle, }: SpeechBalloonProps): JSX.Element;
116
+ /**
117
+ * The conversation in the `history` balloon: a header that counts it and
118
+ * opens it, then every message, the person's to the right, scrolled to the
119
+ * newest.
120
+ */
121
+ /**
122
+ * The history balloon, large (*Expand*): every message, then the tool being
123
+ * called and what goes with the words, in the overlay or the page's area,
124
+ * scrolled there.
125
+ */
126
+ export declare function BalloonHistoryLarge({ history, tool, attachment, }: {
127
+ history: readonly DisplayItem[];
128
+ tool?: BalloonToolLine;
129
+ attachment?: ReactNode;
130
+ }): JSX.Element;
131
+ export declare function SpeechBalloon({ text, more, onOpen, above, side, align, tailAt, approval, decide, onDecisionActive, wide, onDismissPeek, display, tool, speaking, busy, attachment, history, onExpand, expandTitle, suggestions, onSuggestion, waiting, fullText, onExpandHistory, }: SpeechBalloonProps): JSX.Element;
84
132
  export default SpeechBalloon;
@@ -1,13 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { Button, IconButton, Text } from '@primer/react';
4
4
  import { Box } from '@datalayer/primer-addons';
5
5
  import { XIcon } from '@primer/octicons-react';
6
- import { ASSISTANT_WORDS, } from './state';
6
+ import { ASSISTANT_WORDS } from './state';
7
7
  import { moreWaiting } from './ConversationBalloon';
8
8
  import { DecisionAsk } from './DecisionAsk';
9
- import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
9
+ import { BalloonChatItems, BalloonToolCall, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
10
+ import { conversationCount, withToolCall, } from './toolLine';
10
11
  import { BalloonExpandButton } from './BalloonVisual';
12
+ import { ScreenFullIcon } from '@primer/octicons-react';
13
+ /** The suggestions, as chips: each sends its prompt. */
14
+ export function BalloonSuggestions({ suggestions, onSuggestion, }) {
15
+ return (_jsx(Box, { role: "group", "aria-label": "Suggestions", "data-balloon-suggestions": "", sx: { display: 'flex', flexWrap: 'wrap', gap: 1, mt: 2 }, children: suggestions.map(suggestion => (_jsx(Button, { size: "small", "data-balloon-suggestion": suggestion.label, "aria-label": `Ask: ${suggestion.prompt}`, title: suggestion.prompt, onClick: () => onSuggestion(suggestion), sx: { borderRadius: 999 }, children: suggestion.label }, suggestion.label))) }));
16
+ }
11
17
  /** How tall the listed conversation grows before it scrolls, in pixels. */
12
18
  export const BALLOON_HISTORY_MAX_HEIGHT = 220;
13
19
  /** Shorter, when something goes with the words under it (a notebook). */
@@ -17,7 +23,15 @@ export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
17
23
  * opens it, then every message, the person's to the right, scrolled to the
18
24
  * newest.
19
25
  */
20
- function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, }) {
26
+ /**
27
+ * The history balloon, large (*Expand*): every message, then the tool being
28
+ * called and what goes with the words, in the overlay or the page's area,
29
+ * scrolled there.
30
+ */
31
+ export function BalloonHistoryLarge({ history, tool, attachment, }) {
32
+ return (_jsxs(Box, { role: "log", "aria-label": "Conversation", "data-balloon-history-large": "", sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(BalloonChatItems, { items: withToolCall(history, tool), density: "comfortable" }), attachment ? _jsx(Box, { children: attachment }) : null] }));
33
+ }
34
+ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, tool, waiting = false, }) {
21
35
  const listRef = useRef(null);
22
36
  const newest = history[history.length - 1]?.id;
23
37
  useEffect(() => {
@@ -25,7 +39,21 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
25
39
  if (list) {
26
40
  list.scrollTop = list.scrollHeight;
27
41
  }
28
- }, [history.length, newest]);
42
+ }, [history.length, newest, waiting, tool?.id, tool?.phase]);
43
+ // The chat's markdown draws after the list does: kept at the newest as
44
+ // what is drawn grows.
45
+ useEffect(() => {
46
+ const list = listRef.current;
47
+ const content = list?.firstElementChild;
48
+ if (!list || !content || typeof ResizeObserver === 'undefined') {
49
+ return;
50
+ }
51
+ const observer = new ResizeObserver(() => {
52
+ list.scrollTop = list.scrollHeight;
53
+ });
54
+ observer.observe(content);
55
+ return () => observer.disconnect();
56
+ }, []);
29
57
  return (_jsxs(Box, { "data-balloon-history": "", sx: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }, children: [_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-header": "", onClick: onOpen, sx: {
30
58
  m: 0,
31
59
  p: 0,
@@ -39,24 +67,27 @@ function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGH
39
67
  fontWeight: 'semibold',
40
68
  color: 'fg.muted',
41
69
  '&:hover': { textDecoration: 'underline' },
42
- }, children: conversationHeaderText(history.length) }), _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
70
+ }, children: conversationHeaderText(conversationCount(history)) }), onExpand ? (_jsx(IconButton, { icon: ScreenFullIcon, size: "small", variant: "invisible", "aria-label": "Expand the conversation", "data-balloon-history-expand": "", onClick: onExpand, sx: { position: 'absolute', top: '4px', right: '28px' } })) : null, _jsx(Box, { ref: listRef, role: "log", "aria-label": "Conversation", "data-balloon-history-list": "", sx: {
43
71
  maxHeight,
44
72
  overflowY: 'auto',
45
73
  display: 'flex',
46
74
  flexDirection: 'column',
47
- gap: 1,
48
75
  pr: 1,
49
- }, children: history.map(message => (_jsx(Box, { "data-balloon-history-message": message.role, sx: {
50
- alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
51
- maxWidth: '88%',
52
- px: 2,
53
- py: 1,
54
- borderRadius: 2,
55
- bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
56
- overflowWrap: 'anywhere',
57
- }, children: message.text }, message.id))) })] }));
76
+ }, children: _jsx(BalloonChatItems, { items: withToolCall(history, tool), waiting: waiting }) })] }));
58
77
  }
59
- export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, display = 'history', tool, speaking = false, busy = false, attachment, history, onExpand, expandTitle, }) {
78
+ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, approval, decide, onDecisionActive, wide = false, onDismissPeek, display = 'history', tool, speaking = false, busy = false, attachment, history, onExpand, expandTitle, suggestions, onSuggestion, waiting = false, fullText, onExpandHistory, }) {
79
+ // *more*: the words whole, when they were cut or overflow their lines.
80
+ const [whole, setWhole] = useState(false);
81
+ const [overflowing, setOverflowing] = useState(false);
82
+ const cut = (!!fullText && fullText !== text) || (!!more && text.endsWith('…'));
83
+ const wholeText = fullText ?? text;
84
+ // Other words, folded again.
85
+ useEffect(() => {
86
+ setWhole(false);
87
+ }, [wholeText.slice(0, 40)]);
88
+ const offered = suggestions && suggestions.length > 0 && onSuggestion && !approval
89
+ ? suggestions
90
+ : null;
60
91
  const current = display === 'current';
61
92
  // History with messages to list: all of them, not the newest line alone.
62
93
  const listed = !current && history && history.length > 0 ? history : null;
@@ -68,8 +99,14 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
68
99
  ? { bottom: `${above}px` }
69
100
  : { top: `${above}px` }),
70
101
  [align]: 0,
71
- maxWidth: wide || listed || (current && attachment) ? 300 : current ? 260 : 280,
72
- width: wide || listed || (current && attachment) ? 300 : 'max-content',
102
+ maxWidth: wide || listed || offered || whole || (current && attachment)
103
+ ? 300
104
+ : current
105
+ ? 260
106
+ : 280,
107
+ width: wide || listed || offered || whole || (current && attachment)
108
+ ? 300
109
+ : 'max-content',
73
110
  px: 3,
74
111
  py: 2,
75
112
  bg: 'canvas.default',
@@ -106,9 +143,12 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
106
143
  font: 'inherit',
107
144
  textAlign: 'left',
108
145
  cursor: 'pointer',
109
- }, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, maxHeight: attachment
146
+ }, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking, waiting: waiting, whole: whole, fullText: wholeText, onOverflow: setOverflowing }) })) : listed ? (_jsx(BalloonHistory, { history: listed, onOpen: onOpen, onExpand: onExpandHistory, tool: tool, waiting: waiting, maxHeight: attachment
110
147
  ? BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT
111
- : BALLOON_HISTORY_MAX_HEIGHT })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
148
+ : BALLOON_HISTORY_MAX_HEIGHT })) : tool ? (
149
+ // The tool being called: the chat's card, not inside the peek's
150
+ // button (the card is a button of its own).
151
+ _jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: _jsx(BalloonToolCall, { line: tool }) })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
112
152
  flex: 1,
113
153
  minWidth: 0,
114
154
  m: 0,
@@ -120,6 +160,17 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
120
160
  textAlign: 'left',
121
161
  cursor: 'pointer',
122
162
  '&:hover': { textDecoration: 'underline' },
123
- }, children: tool ? (_jsx(BalloonToolLineView, { line: tool })) : (_jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text })) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), listed && tool ? (_jsx(Box, { "data-balloon-history-tool": "", sx: { mt: 1 }, children: _jsx(BalloonToolLineView, { line: tool }) })) : null, approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? 'the visual', onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
163
+ }, children: _jsx(Box, { as: "span", role: "status", "aria-live": "polite", children: text }) })), onDismissPeek && !approval ? (_jsx(IconButton, { icon: XIcon, size: "small", variant: "invisible", "aria-label": "Dismiss", "data-balloon-dismiss": "", onClick: onDismissPeek, sx: { mt: '-4px', mr: '-8px', flexShrink: 0 } })) : null] }), current && !approval && !tool && (cut || overflowing || whole) ? (_jsx(Box, { as: "button", type: "button", "aria-expanded": whole, "data-balloon-whole": whole ? 'less' : 'more', onClick: () => setWhole(!whole), sx: {
164
+ m: 0,
165
+ mt: 1,
166
+ p: 0,
167
+ border: 0,
168
+ bg: 'transparent',
169
+ color: 'accent.fg',
170
+ font: 'inherit',
171
+ fontSize: 0,
172
+ cursor: 'pointer',
173
+ '&:hover': { textDecoration: 'underline' },
174
+ }, children: whole ? 'less' : 'more' })) : null, !current && !listed && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(BalloonChatItems, { items: [], waiting: true }) })) : null, approval && (_jsxs(Box, { "data-balloon-approval": approval.id, sx: { mt: 1 }, children: [_jsx(Text, { as: "p", sx: { m: 0, fontWeight: 'semibold' }, children: approval.asks }), approval.why ? (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 0 }, children: approval.why })) : null, _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: approval.deciding, onClick: approval.onApprove, children: ASSISTANT_WORDS.approve }), _jsx(Button, { size: "small", disabled: approval.deciding, onClick: approval.onDeny, children: ASSISTANT_WORDS.deny })] }), approval.others > 0 ? (_jsx(Text, { as: "p", sx: { m: 0, mt: 1, color: 'fg.muted', fontSize: 0 }, children: moreWaiting(approval.others) })) : null] })), offered && onSuggestion ? (_jsx(BalloonSuggestions, { suggestions: offered, onSuggestion: onSuggestion })) : null, attachment ? (_jsxs(Box, { "data-balloon-attachment": "", sx: { mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }, children: [attachment, onExpand ? (_jsx(BalloonExpandButton, { title: expandTitle ?? 'the visual', onExpand: onExpand })) : null] })) : null, !current ? _jsx(ToolLineAnnouncer, { line: tool }) : null, decide && _jsx(DecisionAsk, { ask: decide, onActiveChange: onDecisionActive })] }));
124
175
  }
125
176
  export default SpeechBalloon;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * What the assistant's menu knows of its agent and of its code sandbox, for
3
+ * *Agent Details…* and *Code Sandbox Details…*.
4
+ *
5
+ * Light on purpose: types and pure helpers only. The dialogs that draw them
6
+ * — `AgentDetails`, jupyter-react's `KernelVariables` — are loaded when one
7
+ * is opened, never with the character.
8
+ *
9
+ * @module chat/assistant/assistantDetails
10
+ */
11
+ import type { Kernel } from '@jupyterlab/services';
12
+ import type { KernelVariablesExecution } from '@datalayer/jupyter-react';
13
+ /** Where a sandbox runs. */
14
+ export type AssistantSandboxKind =
15
+ /** Pyodide, in this page. */
16
+ 'browser'
17
+ /** A Jupyter server on this machine, the agent's own. */
18
+ | 'local'
19
+ /** A Jupyter server elsewhere. */
20
+ | 'jupyter'
21
+ /** A Datalayer cloud runtime. */
22
+ | 'cloud'
23
+ /** In the agent runtime's process, or at a provider (`eval`, `e2b`, …). */
24
+ | 'runtime';
25
+ /** A code sandbox, as much of it as its host knows. */
26
+ export type AssistantSandbox = {
27
+ kind: AssistantSandboxKind;
28
+ /** `running`, `starting`, `idle`, `stopped`, `error`. */
29
+ status?: string;
30
+ /** The sandbox manager's variant: `jupyter-server`, `eval`, `datalayer`, a provider. */
31
+ variant?: string;
32
+ /** The Jupyter server, or the base the sandbox is reached at. Shown without its tokens. */
33
+ url?: string;
34
+ /** The token the Jupyter server wants: used to connect, never shown. */
35
+ token?: string;
36
+ /** The kernel the sandbox runs, on that server. */
37
+ kernelId?: string;
38
+ /** The agent whose sandbox it is (a local server's per-agent sandbox). */
39
+ agentId?: string;
40
+ /** The environment it runs in. */
41
+ environment?: string;
42
+ /** A cloud runtime's identity: its uid, never its pod's name. */
43
+ runtimeUid?: string;
44
+ /** When a cloud runtime ends; `null` when it is not metered. */
45
+ expiresAt?: string | number | null;
46
+ /** What a cloud runtime burns, in credits per hour. */
47
+ burningRate?: number;
48
+ startedAt?: string | number;
49
+ lastActivity?: string | number;
50
+ /** The kernel, when the page talks to it: listed live, widgets drawn. */
51
+ connection?: Kernel.IKernelConnection | null;
52
+ /**
53
+ * The agent-runtimes server whose `/api/v1/sandbox/execute` runs code in
54
+ * the agent's sandbox, when the page has no kernel connection to it.
55
+ */
56
+ serverUrl?: string;
57
+ };
58
+ /** Whether a sandbox is up: one with no status is taken to be. */
59
+ export declare function sandboxIsLive(sandbox: AssistantSandbox): boolean;
60
+ /** What the dialog says of a sandbox that is not running yet. */
61
+ export declare const NO_SANDBOX_YET = "No sandbox is running yet: run a cell or ask the agent to execute code.";
62
+ /** What each kind is called. */
63
+ export declare const SANDBOX_KIND_LABELS: Record<AssistantSandboxKind, string>;
64
+ /** Whether a URL is on this machine. */
65
+ export declare function isLocalUrl(url: string | undefined): boolean;
66
+ /** A URL fit to show: no credentials, no token in its query. */
67
+ export declare function displayUrl(url: string | undefined): string | undefined;
68
+ /**
69
+ * A sandbox from the status its server reports — the Loop's sandbox
70
+ * service, or the agent's codemode status — and what the host adds.
71
+ */
72
+ export declare function assistantSandboxOf(status: Record<string, unknown> | null | undefined, options?: {
73
+ agentId?: string;
74
+ serverUrl?: string;
75
+ state?: string;
76
+ }): AssistantSandbox | undefined;
77
+ /**
78
+ * Run code in an agent's sandbox through its agent-runtimes server
79
+ * (`POST /api/v1/sandbox/execute`), whatever the variant: what a page with
80
+ * no kernel connection to the sandbox lists its variables with.
81
+ */
82
+ export declare function sandboxExecuteOverHttp(serverUrl: string, agentId?: string): (code: string) => Promise<KernelVariablesExecution>;
83
+ /** How long until a moment, for a reader: `1 h 20 min`, `never`. */
84
+ export declare function timeLeft(expiresAt: string | number | null | undefined, now?: number): string | undefined;