@datalayer/agent-runtimes 1.3.73 → 1.3.75
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.
- package/lib/chat/ChatFloating.d.ts +25 -2
- package/lib/chat/ChatFloating.js +80 -12
- package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
- package/lib/chat/assistant/AssistantContextMenu.js +68 -0
- package/lib/chat/assistant/AssistantStage.d.ts +123 -5
- package/lib/chat/assistant/AssistantStage.js +245 -7
- package/lib/chat/assistant/BalloonParts.d.ts +15 -1
- package/lib/chat/assistant/BalloonParts.js +26 -9
- package/lib/chat/assistant/BalloonVisual.d.ts +65 -0
- package/lib/chat/assistant/BalloonVisual.js +58 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +68 -2
- package/lib/chat/assistant/SpeechBalloon.js +113 -7
- package/lib/chat/assistant/state.d.ts +19 -0
- package/lib/chat/assistant/state.js +44 -17
- package/lib/chat/base/ChatBase.js +51 -1
- package/lib/chat/indicators/TypingDots.d.ts +13 -0
- package/lib/chat/indicators/TypingDots.js +21 -0
- package/lib/chat/messages/ChatMessageList.js +1 -15
- package/lib/components/inspector/AgentInspector.d.ts +59 -0
- package/lib/components/inspector/AgentInspector.js +220 -0
- package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
- package/lib/components/inspector/AgentInspectorDialog.js +9 -0
- package/lib/components/inspector/JsonTree.d.ts +24 -0
- package/lib/components/inspector/JsonTree.js +89 -0
- package/lib/components/inspector/a2aInspect.d.ts +56 -0
- package/lib/components/inspector/a2aInspect.js +478 -0
- package/lib/components/inspector/agentInspector.d.ts +179 -0
- package/lib/components/inspector/agentInspector.js +267 -0
- package/lib/components/inspector/chatInspect.d.ts +38 -0
- package/lib/components/inspector/chatInspect.js +65 -0
- package/lib/components/inspector/classify.d.ts +19 -0
- package/lib/components/inspector/classify.js +42 -0
- package/lib/components/inspector/index.d.ts +14 -0
- package/lib/components/inspector/index.js +18 -0
- package/lib/components/teams/A2ATeamGraph.d.ts +54 -3
- package/lib/components/teams/A2ATeamGraph.js +37 -11
- package/lib/components/teams/NotebookPreview.d.ts +11 -2
- package/lib/components/teams/NotebookPreview.js +6 -3
- package/lib/components/teams/NotebookPreviewView.js +9 -1
- package/lib/components/teams/TeamNotebook.d.ts +10 -1
- package/lib/components/teams/TeamNotebook.js +15 -3
- package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
- package/lib/components/teams/jupyterThemeVariables.js +441 -0
- package/lib/components/teams/teamConnections.d.ts +9 -1
- package/lib/components/teams/teamConnections.js +20 -0
- package/lib/components/teams/useA2ATeam.d.ts +17 -4
- package/lib/components/teams/useA2ATeam.js +87 -23
- package/lib/examples/AgentA2ATeamExample.d.ts +16 -2
- package/lib/examples/AgentA2ATeamExample.js +65 -13
- package/lib/examples/{ChatAssistantExample.d.ts → AssistantExample.d.ts} +2 -2
- package/lib/examples/{ChatAssistantExample.js → AssistantExample.js} +69 -7
- package/lib/examples/{ChatAssistantGalleryExample.d.ts → AssistantGalleryExample.d.ts} +2 -2
- package/lib/examples/{ChatAssistantGalleryExample.js → AssistantGalleryExample.js} +45 -13
- package/lib/examples/example-selector.js +4 -2
- package/lib/examples/exampleGroups.d.ts +9 -0
- package/lib/examples/exampleGroups.js +56 -0
- package/lib/examples/index.d.ts +1 -1
- package/lib/examples/index.js +1 -1
- package/lib/examples/main.js +3 -43
- package/lib/examples/utils/AssistantGalleryGrid.d.ts +6 -1
- package/lib/examples/utils/AssistantGalleryGrid.js +16 -3
- package/lib/examples/utils/assistantGallery.d.ts +51 -2
- package/lib/examples/utils/assistantGallery.js +32 -7
- package/lib/loop/apps/AppRenderer.js +1 -1
- package/lib/loop/core/index.d.ts +17 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/plugins/assistant/index.d.ts +1 -1
- package/lib/loop/plugins/assistant/index.js +8 -3
- package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
- package/lib/types/chat.d.ts +7 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
|
@@ -1,13 +1,100 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
3
|
import { Button, IconButton, Text } from '@primer/react';
|
|
3
4
|
import { Box } from '@datalayer/primer-addons';
|
|
4
5
|
import { XIcon } from '@primer/octicons-react';
|
|
5
|
-
import { ASSISTANT_WORDS } from './state';
|
|
6
|
+
import { ASSISTANT_WORDS, } from './state';
|
|
6
7
|
import { moreWaiting } from './ConversationBalloon';
|
|
7
8
|
import { DecisionAsk } from './DecisionAsk';
|
|
8
|
-
import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, } from './BalloonParts';
|
|
9
|
-
|
|
9
|
+
import { BalloonToolLineView, CurrentBalloonBody, ToolLineAnnouncer, conversationHeaderText, } from './BalloonParts';
|
|
10
|
+
import { BalloonExpandButton } from './BalloonVisual';
|
|
11
|
+
import { ScreenFullIcon } from '@primer/octicons-react';
|
|
12
|
+
import { TypingDots } from '../indicators/TypingDots';
|
|
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
|
+
}
|
|
17
|
+
/** How tall the listed conversation grows before it scrolls, in pixels. */
|
|
18
|
+
export const BALLOON_HISTORY_MAX_HEIGHT = 220;
|
|
19
|
+
/** Shorter, when something goes with the words under it (a notebook). */
|
|
20
|
+
export const BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT = 120;
|
|
21
|
+
/**
|
|
22
|
+
* The conversation in the `history` balloon: a header that counts it and
|
|
23
|
+
* opens it, then every message, the person's to the right, scrolled to the
|
|
24
|
+
* newest.
|
|
25
|
+
*/
|
|
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, fontSize: 1 }, children: [history.map(message => (_jsx(Box, { "data-balloon-history-message": message.role, sx: {
|
|
33
|
+
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
34
|
+
maxWidth: '80%',
|
|
35
|
+
px: 3,
|
|
36
|
+
py: 2,
|
|
37
|
+
borderRadius: 2,
|
|
38
|
+
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
39
|
+
whiteSpace: 'pre-wrap',
|
|
40
|
+
overflowWrap: 'anywhere',
|
|
41
|
+
}, children: message.text }, message.id))), tool ? _jsx(BalloonToolLineView, { line: tool }) : null, attachment ? _jsx(Box, { children: attachment }) : null] }));
|
|
42
|
+
}
|
|
43
|
+
function BalloonHistory({ history, onOpen, maxHeight = BALLOON_HISTORY_MAX_HEIGHT, onExpand, }) {
|
|
44
|
+
const listRef = useRef(null);
|
|
45
|
+
const newest = history[history.length - 1]?.id;
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const list = listRef.current;
|
|
48
|
+
if (list) {
|
|
49
|
+
list.scrollTop = list.scrollHeight;
|
|
50
|
+
}
|
|
51
|
+
}, [history.length, newest]);
|
|
52
|
+
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: {
|
|
53
|
+
m: 0,
|
|
54
|
+
p: 0,
|
|
55
|
+
pb: 1,
|
|
56
|
+
border: 0,
|
|
57
|
+
bg: 'transparent',
|
|
58
|
+
font: 'inherit',
|
|
59
|
+
textAlign: 'left',
|
|
60
|
+
cursor: 'pointer',
|
|
61
|
+
fontSize: 0,
|
|
62
|
+
fontWeight: 'semibold',
|
|
63
|
+
color: 'fg.muted',
|
|
64
|
+
'&:hover': { textDecoration: 'underline' },
|
|
65
|
+
}, children: conversationHeaderText(history.length) }), 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: {
|
|
66
|
+
maxHeight,
|
|
67
|
+
overflowY: 'auto',
|
|
68
|
+
display: 'flex',
|
|
69
|
+
flexDirection: 'column',
|
|
70
|
+
gap: 1,
|
|
71
|
+
pr: 1,
|
|
72
|
+
}, children: history.map(message => (_jsx(Box, { "data-balloon-history-message": message.role, sx: {
|
|
73
|
+
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
74
|
+
maxWidth: '88%',
|
|
75
|
+
px: 2,
|
|
76
|
+
py: 1,
|
|
77
|
+
borderRadius: 2,
|
|
78
|
+
bg: message.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
79
|
+
overflowWrap: 'anywhere',
|
|
80
|
+
}, children: message.text }, message.id))) })] }));
|
|
81
|
+
}
|
|
82
|
+
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, }) {
|
|
83
|
+
// *more*: the words whole, when they were cut or overflow their lines.
|
|
84
|
+
const [whole, setWhole] = useState(false);
|
|
85
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
86
|
+
const cut = (!!fullText && fullText !== text) || (!!more && text.endsWith('…'));
|
|
87
|
+
const wholeText = fullText ?? text;
|
|
88
|
+
// Other words, folded again.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
setWhole(false);
|
|
91
|
+
}, [wholeText.slice(0, 40)]);
|
|
92
|
+
const offered = suggestions && suggestions.length > 0 && onSuggestion && !approval
|
|
93
|
+
? suggestions
|
|
94
|
+
: null;
|
|
10
95
|
const current = display === 'current';
|
|
96
|
+
// History with messages to list: all of them, not the newest line alone.
|
|
97
|
+
const listed = !current && history && history.length > 0 ? history : null;
|
|
11
98
|
return (_jsxs(Box, { "data-speech-balloon": "", "data-balloon-display": display, sx: {
|
|
12
99
|
position: 'absolute',
|
|
13
100
|
// Pixels, as strings: a number here is read as the theme's space
|
|
@@ -16,8 +103,14 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
16
103
|
? { bottom: `${above}px` }
|
|
17
104
|
: { top: `${above}px` }),
|
|
18
105
|
[align]: 0,
|
|
19
|
-
maxWidth: wide ||
|
|
20
|
-
|
|
106
|
+
maxWidth: wide || listed || offered || whole || (current && attachment)
|
|
107
|
+
? 300
|
|
108
|
+
: current
|
|
109
|
+
? 260
|
|
110
|
+
: 280,
|
|
111
|
+
width: wide || listed || offered || whole || (current && attachment)
|
|
112
|
+
? 300
|
|
113
|
+
: 'max-content',
|
|
21
114
|
px: 3,
|
|
22
115
|
py: 2,
|
|
23
116
|
bg: 'canvas.default',
|
|
@@ -54,7 +147,9 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
54
147
|
font: 'inherit',
|
|
55
148
|
textAlign: 'left',
|
|
56
149
|
cursor: 'pointer',
|
|
57
|
-
}, children: _jsx(CurrentBalloonBody, { text: text, tool: tool, busy: busy, speaking: speaking
|
|
150
|
+
}, 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, maxHeight: attachment
|
|
151
|
+
? BALLOON_HISTORY_WITH_ATTACHMENT_MAX_HEIGHT
|
|
152
|
+
: BALLOON_HISTORY_MAX_HEIGHT })) : (_jsx(Box, { as: "button", type: "button", "data-balloon-peek": "", "data-balloon-more": more ? '' : undefined, onClick: onOpen, sx: {
|
|
58
153
|
flex: 1,
|
|
59
154
|
minWidth: 0,
|
|
60
155
|
m: 0,
|
|
@@ -66,6 +161,17 @@ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above
|
|
|
66
161
|
textAlign: 'left',
|
|
67
162
|
cursor: 'pointer',
|
|
68
163
|
'&:hover': { textDecoration: 'underline' },
|
|
69
|
-
}, 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] }),
|
|
164
|
+
}, 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] }), 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: {
|
|
165
|
+
m: 0,
|
|
166
|
+
mt: 1,
|
|
167
|
+
p: 0,
|
|
168
|
+
border: 0,
|
|
169
|
+
bg: 'transparent',
|
|
170
|
+
color: 'accent.fg',
|
|
171
|
+
font: 'inherit',
|
|
172
|
+
fontSize: 0,
|
|
173
|
+
cursor: 'pointer',
|
|
174
|
+
'&:hover': { textDecoration: 'underline' },
|
|
175
|
+
}, children: whole ? 'less' : 'more' })) : null, listed && tool ? (_jsx(Box, { "data-balloon-history-tool": "", sx: { mt: 1 }, children: _jsx(BalloonToolLineView, { line: tool }) })) : null, !current && waiting ? (_jsx(Box, { "data-balloon-waiting": "", sx: { mt: 1 }, children: _jsx(TypingDots, { size: 6 }) })) : 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 })] }));
|
|
70
176
|
}
|
|
71
177
|
export default SpeechBalloon;
|
|
@@ -79,6 +79,25 @@ export interface AssistantSaying {
|
|
|
79
79
|
}
|
|
80
80
|
/** How much of a message is kept as the agent's saying before it is cut. */
|
|
81
81
|
export declare const SAYING_LIMIT = 220;
|
|
82
|
+
/**
|
|
83
|
+
* A message's content as plain words, for a balloon: its text parts joined,
|
|
84
|
+
* code blocks left out, links and Markdown read as plain words.
|
|
85
|
+
*/
|
|
86
|
+
export declare function plainWordsOf(content: unknown): string;
|
|
87
|
+
/** A message of the conversation, as the `history` balloon lists it. */
|
|
88
|
+
export interface BalloonHistoryMessage {
|
|
89
|
+
id: string;
|
|
90
|
+
/** Who said it: the person, or the agent. */
|
|
91
|
+
role: 'user' | 'assistant';
|
|
92
|
+
/** Its words, plain. */
|
|
93
|
+
text: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The conversation as the `history` balloon lists it (T-23): the person's
|
|
97
|
+
* and the agent's messages with words, in order — not the tool calls, not
|
|
98
|
+
* an empty message — the same messages its header counts.
|
|
99
|
+
*/
|
|
100
|
+
export declare function balloonHistoryOf(items: readonly unknown[]): BalloonHistoryMessage[];
|
|
82
101
|
/**
|
|
83
102
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
84
103
|
* balloon, while the conversation is closed (T-23). Only its own messages
|
|
@@ -52,6 +52,49 @@ export const ASSISTANT_WORDS = {
|
|
|
52
52
|
};
|
|
53
53
|
/** How much of a message is kept as the agent's saying before it is cut. */
|
|
54
54
|
export const SAYING_LIMIT = 220;
|
|
55
|
+
/**
|
|
56
|
+
* A message's content as plain words, for a balloon: its text parts joined,
|
|
57
|
+
* code blocks left out, links and Markdown read as plain words.
|
|
58
|
+
*/
|
|
59
|
+
export function plainWordsOf(content) {
|
|
60
|
+
const raw = typeof content === 'string'
|
|
61
|
+
? content
|
|
62
|
+
: Array.isArray(content)
|
|
63
|
+
? content
|
|
64
|
+
.map(part => part &&
|
|
65
|
+
typeof part === 'object' &&
|
|
66
|
+
part.type === 'text'
|
|
67
|
+
? String(part.text ?? '')
|
|
68
|
+
: '')
|
|
69
|
+
.join(' ')
|
|
70
|
+
: '';
|
|
71
|
+
return raw
|
|
72
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
73
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
74
|
+
.replace(/[*_`#>]+/g, '')
|
|
75
|
+
.replace(/\s+/g, ' ')
|
|
76
|
+
.trim();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The conversation as the `history` balloon lists it (T-23): the person's
|
|
80
|
+
* and the agent's messages with words, in order — not the tool calls, not
|
|
81
|
+
* an empty message — the same messages its header counts.
|
|
82
|
+
*/
|
|
83
|
+
export function balloonHistoryOf(items) {
|
|
84
|
+
const history = [];
|
|
85
|
+
items.forEach((entry, index) => {
|
|
86
|
+
const item = entry;
|
|
87
|
+
if ((item.role !== 'user' && item.role !== 'assistant') ||
|
|
88
|
+
typeof item.toolName === 'string') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const text = plainWordsOf(item.content);
|
|
92
|
+
if (text) {
|
|
93
|
+
history.push({ id: String(item.id ?? index), role: item.role, text });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return history;
|
|
97
|
+
}
|
|
55
98
|
/**
|
|
56
99
|
* The newest thing the agent said, as the Office Assistant said it: in the
|
|
57
100
|
* balloon, while the conversation is closed (T-23). Only its own messages
|
|
@@ -64,23 +107,7 @@ export function latestSaying(items) {
|
|
|
64
107
|
if (item.role !== 'assistant' || typeof item.toolName === 'string') {
|
|
65
108
|
continue;
|
|
66
109
|
}
|
|
67
|
-
const
|
|
68
|
-
? item.content
|
|
69
|
-
: Array.isArray(item.content)
|
|
70
|
-
? item.content
|
|
71
|
-
.map(part => part &&
|
|
72
|
-
typeof part === 'object' &&
|
|
73
|
-
part.type === 'text'
|
|
74
|
-
? String(part.text ?? '')
|
|
75
|
-
: '')
|
|
76
|
-
.join(' ')
|
|
77
|
-
: '';
|
|
78
|
-
const plain = raw
|
|
79
|
-
.replace(/```[\s\S]*?```/g, ' ')
|
|
80
|
-
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
81
|
-
.replace(/[*_`#>]+/g, '')
|
|
82
|
-
.replace(/\s+/g, ' ')
|
|
83
|
-
.trim();
|
|
110
|
+
const plain = plainWordsOf(item.content);
|
|
84
111
|
if (!plain) {
|
|
85
112
|
continue;
|
|
86
113
|
}
|
|
@@ -58,6 +58,8 @@ const EphemeralDocument = React.lazy(() => import('../document/EphemeralDocument
|
|
|
58
58
|
default: m.EphemeralDocument,
|
|
59
59
|
})));
|
|
60
60
|
import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHooks';
|
|
61
|
+
import { useAgentInspectorSink } from '../../components/inspector/agentInspector';
|
|
62
|
+
import { chatInspectorRecorder, } from '../../components/inspector/chatInspect';
|
|
61
63
|
// Tracks pending prompts already auto-sent for a given conversation scope.
|
|
62
64
|
// This prevents layout-driven unmount/remount cycles from re-sending prompts.
|
|
63
65
|
const sentPendingPromptKeys = new Set();
|
|
@@ -513,7 +515,7 @@ useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady
|
|
|
513
515
|
// Extended props
|
|
514
516
|
brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderToolResult, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
|
|
515
517
|
// Tool invocation hooks
|
|
516
|
-
onToolCallStart, onToolCallComplete,
|
|
518
|
+
onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
|
|
517
519
|
// Identity/Authorization props
|
|
518
520
|
onAuthorizationRequired: _onAuthorizationRequired, connectedIdentities,
|
|
519
521
|
// Conversation persistence
|
|
@@ -1589,6 +1591,25 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
1589
1591
|
onToolCallStartRef.current = onToolCallStart;
|
|
1590
1592
|
const onToolCallCompleteRef = useRef(onToolCallComplete);
|
|
1591
1593
|
onToolCallCompleteRef.current = onToolCallComplete;
|
|
1594
|
+
// The Agent Inspector: the turns and tool calls, recorded as they happen.
|
|
1595
|
+
const inspectorFromContext = useAgentInspectorSink();
|
|
1596
|
+
const inspectorSink = inspectorGiven ?? inspectorFromContext;
|
|
1597
|
+
const inspectorActorRef = useRef('Agent');
|
|
1598
|
+
inspectorActorRef.current =
|
|
1599
|
+
(typeof title === 'string' && title) || activeAgentId || 'Agent';
|
|
1600
|
+
const recorderRef = useRef(null);
|
|
1601
|
+
if (inspectorSink && recorderRef.current?.sink !== inspectorSink) {
|
|
1602
|
+
recorderRef.current = {
|
|
1603
|
+
sink: inspectorSink,
|
|
1604
|
+
recorder: chatInspectorRecorder(inspectorSink, () => inspectorActorRef.current, {
|
|
1605
|
+
frontendTools: () => (frontendToolsRef.current ?? []).map(tool => tool.name),
|
|
1606
|
+
}),
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
else if (!inspectorSink) {
|
|
1610
|
+
recorderRef.current = null;
|
|
1611
|
+
}
|
|
1612
|
+
const inspectAnswerRef = useRef('');
|
|
1592
1613
|
const handleRespondRef = useRef(null);
|
|
1593
1614
|
// A decision made outside the chat — a host page's own approval banner —
|
|
1594
1615
|
// is answered exactly as the tool card's buttons answer it: through the
|
|
@@ -2415,6 +2436,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2415
2436
|
const sanitizedContent = typeof rawContent === 'string'
|
|
2416
2437
|
? sanitizeAssistantContent(rawContent)
|
|
2417
2438
|
: (rawContent ?? '');
|
|
2439
|
+
if (typeof sanitizedContent === 'string' && sanitizedContent) {
|
|
2440
|
+
inspectAnswerRef.current = sanitizedContent;
|
|
2441
|
+
}
|
|
2418
2442
|
setDisplayItems(prev => {
|
|
2419
2443
|
const newItems = [...prev];
|
|
2420
2444
|
const idx = newItems.findIndex(item => !isToolCallMessage(item) && item.id === currentId);
|
|
@@ -2520,6 +2544,11 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2520
2544
|
toolCallId,
|
|
2521
2545
|
args,
|
|
2522
2546
|
});
|
|
2547
|
+
recorderRef.current?.recorder.toolStarted({
|
|
2548
|
+
id: toolCallId,
|
|
2549
|
+
name: toolName,
|
|
2550
|
+
args,
|
|
2551
|
+
});
|
|
2523
2552
|
const frontendTool = frontendToolsRef.current?.find(t => t.name === toolName);
|
|
2524
2553
|
const toolHandler = frontendTool?.handler;
|
|
2525
2554
|
// Execute when the args are complete. The protocol says so
|
|
@@ -2604,6 +2633,13 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2604
2633
|
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
2605
2634
|
? updatedToolCall
|
|
2606
2635
|
: item));
|
|
2636
|
+
recorderRef.current?.recorder.toolEnded(toolCallId, {
|
|
2637
|
+
result: event.toolResult.result,
|
|
2638
|
+
error: event.toolResult.error ??
|
|
2639
|
+
(updatedToolCall.status === 'error'
|
|
2640
|
+
? (executionError ?? 'failed')
|
|
2641
|
+
: undefined),
|
|
2642
|
+
});
|
|
2607
2643
|
// Fire post-hook for tool results
|
|
2608
2644
|
onToolCallCompleteRef.current?.({
|
|
2609
2645
|
toolName: existingToolCall.toolName,
|
|
@@ -2657,6 +2693,11 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2657
2693
|
* The bar and its ring simply never appeared for a browser agent.
|
|
2658
2694
|
*/
|
|
2659
2695
|
{
|
|
2696
|
+
recorderRef.current?.recorder.turnEnded({
|
|
2697
|
+
text: inspectAnswerRef.current || undefined,
|
|
2698
|
+
usage: event.usage,
|
|
2699
|
+
});
|
|
2700
|
+
inspectAnswerRef.current = '';
|
|
2660
2701
|
const turnInput = event.usage?.promptTokens ?? 0;
|
|
2661
2702
|
const turnOutput = event.usage?.completionTokens ?? 0;
|
|
2662
2703
|
// The parts sum to the total when the harness did not send one:
|
|
@@ -2710,6 +2751,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2710
2751
|
break;
|
|
2711
2752
|
case 'error':
|
|
2712
2753
|
console.error('[ChatBase] Protocol error:', event.error);
|
|
2754
|
+
recorderRef.current?.recorder.turnEnded({
|
|
2755
|
+
error: event.error?.message ?? 'error',
|
|
2756
|
+
});
|
|
2713
2757
|
if (event.error?.message &&
|
|
2714
2758
|
/exceeded maximum retries/i.test(event.error.message) &&
|
|
2715
2759
|
hideMessagesAfterToolUIRef.current) {
|
|
@@ -2777,6 +2821,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2777
2821
|
result,
|
|
2778
2822
|
status: 'complete',
|
|
2779
2823
|
};
|
|
2824
|
+
recorderRef.current?.recorder.toolEnded(toolCallId, { result });
|
|
2780
2825
|
toolCallsRef.current.set(toolCallId, completedToolCall);
|
|
2781
2826
|
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
2782
2827
|
? completedToolCall
|
|
@@ -2789,6 +2834,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2789
2834
|
status: 'error',
|
|
2790
2835
|
error: err.message,
|
|
2791
2836
|
};
|
|
2837
|
+
recorderRef.current?.recorder.toolEnded(toolCallId, {
|
|
2838
|
+
error: err.message,
|
|
2839
|
+
});
|
|
2792
2840
|
toolCallsRef.current.set(toolCallId, errorToolCall);
|
|
2793
2841
|
setDisplayItems(prev => prev.map(item => isToolCallMessage(item) && item.toolCallId === toolCallId
|
|
2794
2842
|
? errorToolCall
|
|
@@ -2838,6 +2886,8 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2838
2886
|
if (!adapterRef.current && !onSendMessage)
|
|
2839
2887
|
return;
|
|
2840
2888
|
stoppedRef.current = false;
|
|
2889
|
+
inspectAnswerRef.current = '';
|
|
2890
|
+
recorderRef.current?.recorder.turnStarted(messageContent, selectedModel || undefined);
|
|
2841
2891
|
suppressAssistantTextForToolOnlyRef.current =
|
|
2842
2892
|
isToolCallOnlyPrompt(messageContent);
|
|
2843
2893
|
// Said, then sent: by the microphone at once, or from the composer it
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three pulsing dots: a speaker is still at work and has not written yet —
|
|
3
|
+
* the chat's waiting indicator, and the assistant's balloon's. Still, but
|
|
4
|
+
* shown, for a reader who asks for reduced motion. Hidden from a screen
|
|
5
|
+
* reader: the live region beside it says the state.
|
|
6
|
+
*
|
|
7
|
+
* @module chat/indicators/TypingDots
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactElement } from 'react';
|
|
10
|
+
export declare function TypingDots({ size }: {
|
|
11
|
+
size?: number;
|
|
12
|
+
}): ReactElement;
|
|
13
|
+
export default TypingDots;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from '@datalayer/primer-addons';
|
|
3
|
+
export function TypingDots({ size = 8 }) {
|
|
4
|
+
return (_jsx(Box, { "aria-hidden": "true", "data-typing-dots": "", sx: { display: 'inline-flex', alignItems: 'center', gap: '4px' }, children: [0, 0.2, 0.4].map((delay, index) => (_jsx(Box, { sx: {
|
|
5
|
+
width: size,
|
|
6
|
+
height: size,
|
|
7
|
+
borderRadius: '50%',
|
|
8
|
+
bg: 'fg.muted',
|
|
9
|
+
animation: 'typingPulse 1.4s ease-in-out infinite',
|
|
10
|
+
animationDelay: `${delay}s`,
|
|
11
|
+
'@keyframes typingPulse': {
|
|
12
|
+
'0%, 60%, 100%': { transform: 'scale(0.6)', opacity: 0.4 },
|
|
13
|
+
'30%': { transform: 'scale(1)', opacity: 1 },
|
|
14
|
+
},
|
|
15
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
16
|
+
animation: 'none',
|
|
17
|
+
opacity: 0.7,
|
|
18
|
+
},
|
|
19
|
+
} }, index))) }));
|
|
20
|
+
}
|
|
21
|
+
export default TypingDots;
|
|
@@ -18,6 +18,7 @@ import { Streamdown } from 'streamdown';
|
|
|
18
18
|
import { streamdownMarkdownStyles, streamdownCodeBlockStyles, } from '../styles/streamdownStyles';
|
|
19
19
|
import { ToolCallDisplay } from '../tools/ToolCallDisplay';
|
|
20
20
|
import { marksOfToolCall, skillIdOfCall } from '../marks';
|
|
21
|
+
import { TypingDots } from '../indicators/TypingDots';
|
|
21
22
|
import { TurnFooter } from './TurnFooter';
|
|
22
23
|
import { normalizeAssistantMarkdown } from './assistantMarkdown';
|
|
23
24
|
import { parseFormSubmission } from './formSubmission';
|
|
@@ -62,21 +63,6 @@ function toneColorsOf(tone) {
|
|
|
62
63
|
fg: `${tone}.fg`,
|
|
63
64
|
};
|
|
64
65
|
}
|
|
65
|
-
/** Three pulsing dots: a speaker is still writing and has not settled yet. */
|
|
66
|
-
function TypingDots({ size = 8 }) {
|
|
67
|
-
return (_jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: '4px' }, children: [0, 0.2, 0.4].map((delay, index) => (_jsx(Box, { sx: {
|
|
68
|
-
width: size,
|
|
69
|
-
height: size,
|
|
70
|
-
borderRadius: '50%',
|
|
71
|
-
bg: 'fg.muted',
|
|
72
|
-
animation: 'typingPulse 1.4s ease-in-out infinite',
|
|
73
|
-
animationDelay: `${delay}s`,
|
|
74
|
-
'@keyframes typingPulse': {
|
|
75
|
-
'0%, 60%, 100%': { transform: 'scale(0.6)', opacity: 0.4 },
|
|
76
|
-
'30%': { transform: 'scale(1)', opacity: 1 },
|
|
77
|
-
},
|
|
78
|
-
} }, index))) }));
|
|
79
|
-
}
|
|
80
66
|
const SUBAGENT_AVATAR_CONFIG = {
|
|
81
67
|
userAvatar: _jsx(PersonIcon, { size: 14 }),
|
|
82
68
|
assistantAvatar: _jsx(DependabotIcon, { size: 14 }),
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent Inspector: what agents do, one row a thing, newest last — the
|
|
3
|
+
* time (to the millisecond, and since the first), who did it and toward
|
|
4
|
+
* whom, its source and kind, its name with its mark, its status and
|
|
5
|
+
* duration, and a line that says it. A row unfolds to its payload as a JSON
|
|
6
|
+
* tree (a call's arguments and its result), and to what was made of it.
|
|
7
|
+
*
|
|
8
|
+
* Filters by source, kind and actor; *Pause* holds the rows still while the
|
|
9
|
+
* record goes on, *Clear* empties it, *Export* saves the session as JSON. A
|
|
10
|
+
* large payload (a notebook) says its size and stays folded until asked.
|
|
11
|
+
*
|
|
12
|
+
* Fed by a sink (`useAgentInspector`) or given its entries.
|
|
13
|
+
*
|
|
14
|
+
* @module components/inspector/AgentInspector
|
|
15
|
+
*/
|
|
16
|
+
import type { JSX } from 'react';
|
|
17
|
+
import { type AgentInspectorEntry, type AgentInspectorExport, type AgentInspectorSink, type AgentInspectorSource } from './agentInspector';
|
|
18
|
+
export type AgentInspectorProps = {
|
|
19
|
+
/** The record to show, kept current. */
|
|
20
|
+
sink?: AgentInspectorSink | null;
|
|
21
|
+
/** Or its entries, as given. */
|
|
22
|
+
entries?: readonly AgentInspectorEntry[];
|
|
23
|
+
/** How tall the rows grow before they scroll. */
|
|
24
|
+
maxHeight?: number | string;
|
|
25
|
+
/** Payloads larger than this, in bytes, stay folded until asked. */
|
|
26
|
+
largeBytes?: number;
|
|
27
|
+
/** What is said while nothing is recorded. */
|
|
28
|
+
emptyText?: string;
|
|
29
|
+
/** The file name the session is exported as, without `.json`. */
|
|
30
|
+
exportName?: string;
|
|
31
|
+
/**
|
|
32
|
+
* One agent's own record: what it did (`actor`), and the A2A messages it
|
|
33
|
+
* sent or received — one message shows in the sender's and in the
|
|
34
|
+
* receiver's, the same entry, as sent in one and received in the other.
|
|
35
|
+
*/
|
|
36
|
+
agent?: string;
|
|
37
|
+
};
|
|
38
|
+
/** Whether an entry is one agent's: done by it, or sent to or by it. */
|
|
39
|
+
export declare function isAgentsEntry(entry: AgentInspectorEntry, agent: string): boolean;
|
|
40
|
+
/** Which way an entry went, from one agent's side: `→ Accounting`, `← Accounting`. */
|
|
41
|
+
export declare function directionFrom(entry: AgentInspectorEntry, agent: string | undefined): string;
|
|
42
|
+
/** A time of day, to the millisecond: `15:44:31.299`. */
|
|
43
|
+
export declare function clockTime(at: number): string;
|
|
44
|
+
/** Time since the first entry: `+1.204s`. */
|
|
45
|
+
export declare function sinceFirst(at: number, first: number): string;
|
|
46
|
+
/** How long a call took: `412 ms`, `3.4 s`. */
|
|
47
|
+
export declare function durationText(ms: number): string;
|
|
48
|
+
export type AgentInspectorFilter = {
|
|
49
|
+
sources: ReadonlySet<AgentInspectorSource>;
|
|
50
|
+
kinds: ReadonlySet<string>;
|
|
51
|
+
actors: ReadonlySet<string>;
|
|
52
|
+
};
|
|
53
|
+
/** The entries a filter keeps: an empty set keeps every value. */
|
|
54
|
+
export declare function filterEntries(entries: readonly AgentInspectorEntry[], filter: AgentInspectorFilter): AgentInspectorEntry[];
|
|
55
|
+
/** A session, as JSON. */
|
|
56
|
+
export declare function exportEntries(entries: readonly AgentInspectorEntry[]): AgentInspectorExport;
|
|
57
|
+
/** The Agent Inspector. */
|
|
58
|
+
export declare function AgentInspector({ sink, entries: given, maxHeight, largeBytes, emptyText, exportName, agent, }: AgentInspectorProps): JSX.Element;
|
|
59
|
+
export default AgentInspector;
|