@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.
Files changed (72) hide show
  1. package/lib/chat/ChatFloating.d.ts +25 -2
  2. package/lib/chat/ChatFloating.js +80 -12
  3. package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
  4. package/lib/chat/assistant/AssistantContextMenu.js +68 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +123 -5
  6. package/lib/chat/assistant/AssistantStage.js +245 -7
  7. package/lib/chat/assistant/BalloonParts.d.ts +15 -1
  8. package/lib/chat/assistant/BalloonParts.js +26 -9
  9. package/lib/chat/assistant/BalloonVisual.d.ts +65 -0
  10. package/lib/chat/assistant/BalloonVisual.js +58 -0
  11. package/lib/chat/assistant/SpeechBalloon.d.ts +68 -2
  12. package/lib/chat/assistant/SpeechBalloon.js +113 -7
  13. package/lib/chat/assistant/state.d.ts +19 -0
  14. package/lib/chat/assistant/state.js +44 -17
  15. package/lib/chat/base/ChatBase.js +51 -1
  16. package/lib/chat/indicators/TypingDots.d.ts +13 -0
  17. package/lib/chat/indicators/TypingDots.js +21 -0
  18. package/lib/chat/messages/ChatMessageList.js +1 -15
  19. package/lib/components/inspector/AgentInspector.d.ts +59 -0
  20. package/lib/components/inspector/AgentInspector.js +220 -0
  21. package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
  22. package/lib/components/inspector/AgentInspectorDialog.js +9 -0
  23. package/lib/components/inspector/JsonTree.d.ts +24 -0
  24. package/lib/components/inspector/JsonTree.js +89 -0
  25. package/lib/components/inspector/a2aInspect.d.ts +56 -0
  26. package/lib/components/inspector/a2aInspect.js +478 -0
  27. package/lib/components/inspector/agentInspector.d.ts +179 -0
  28. package/lib/components/inspector/agentInspector.js +267 -0
  29. package/lib/components/inspector/chatInspect.d.ts +38 -0
  30. package/lib/components/inspector/chatInspect.js +65 -0
  31. package/lib/components/inspector/classify.d.ts +19 -0
  32. package/lib/components/inspector/classify.js +42 -0
  33. package/lib/components/inspector/index.d.ts +14 -0
  34. package/lib/components/inspector/index.js +18 -0
  35. package/lib/components/teams/A2ATeamGraph.d.ts +54 -3
  36. package/lib/components/teams/A2ATeamGraph.js +37 -11
  37. package/lib/components/teams/NotebookPreview.d.ts +11 -2
  38. package/lib/components/teams/NotebookPreview.js +6 -3
  39. package/lib/components/teams/NotebookPreviewView.js +9 -1
  40. package/lib/components/teams/TeamNotebook.d.ts +10 -1
  41. package/lib/components/teams/TeamNotebook.js +15 -3
  42. package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
  43. package/lib/components/teams/jupyterThemeVariables.js +441 -0
  44. package/lib/components/teams/teamConnections.d.ts +9 -1
  45. package/lib/components/teams/teamConnections.js +20 -0
  46. package/lib/components/teams/useA2ATeam.d.ts +17 -4
  47. package/lib/components/teams/useA2ATeam.js +87 -23
  48. package/lib/examples/AgentA2ATeamExample.d.ts +16 -2
  49. package/lib/examples/AgentA2ATeamExample.js +65 -13
  50. package/lib/examples/{ChatAssistantExample.d.ts → AssistantExample.d.ts} +2 -2
  51. package/lib/examples/{ChatAssistantExample.js → AssistantExample.js} +69 -7
  52. package/lib/examples/{ChatAssistantGalleryExample.d.ts → AssistantGalleryExample.d.ts} +2 -2
  53. package/lib/examples/{ChatAssistantGalleryExample.js → AssistantGalleryExample.js} +45 -13
  54. package/lib/examples/example-selector.js +4 -2
  55. package/lib/examples/exampleGroups.d.ts +9 -0
  56. package/lib/examples/exampleGroups.js +56 -0
  57. package/lib/examples/index.d.ts +1 -1
  58. package/lib/examples/index.js +1 -1
  59. package/lib/examples/main.js +3 -43
  60. package/lib/examples/utils/AssistantGalleryGrid.d.ts +6 -1
  61. package/lib/examples/utils/AssistantGalleryGrid.js +16 -3
  62. package/lib/examples/utils/assistantGallery.d.ts +51 -2
  63. package/lib/examples/utils/assistantGallery.js +32 -7
  64. package/lib/loop/apps/AppRenderer.js +1 -1
  65. package/lib/loop/core/index.d.ts +17 -0
  66. package/lib/loop/core/index.js +1 -0
  67. package/lib/loop/plugins/assistant/index.d.ts +1 -1
  68. package/lib/loop/plugins/assistant/index.js +8 -3
  69. package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
  70. package/lib/types/chat.d.ts +7 -0
  71. package/package.json +1 -1
  72. package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
@@ -0,0 +1,220 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { ActionList, ActionMenu, Button, Label, Text } from '@primer/react';
4
+ import { Box } from '@datalayer/primer-addons';
5
+ import { DownloadIcon, PlayIcon, TrashIcon, PauseIcon, } from '@primer/octicons-react';
6
+ import { SpecMark } from '../../chat/marks';
7
+ import { AGENT_INSPECTOR_SOURCES, AGENT_INSPECTOR_SOURCE_LABELS, formatBytes, useAgentInspectorEntries, } from './agentInspector';
8
+ import { JsonTree } from './JsonTree';
9
+ /** Whether an entry is one agent's: done by it, or sent to or by it. */
10
+ export function isAgentsEntry(entry, agent) {
11
+ return (entry.actor === agent ||
12
+ entry.direction?.from === agent ||
13
+ entry.direction?.to === agent);
14
+ }
15
+ /** Which way an entry went, from one agent's side: `→ Accounting`, `← Accounting`. */
16
+ export function directionFrom(entry, agent) {
17
+ const direction = entry.direction;
18
+ if (!direction) {
19
+ return entry.actor;
20
+ }
21
+ if (agent === direction.from) {
22
+ return `sent → ${direction.to}`;
23
+ }
24
+ if (agent === direction.to) {
25
+ return `received ← ${direction.from}`;
26
+ }
27
+ return `${direction.from} → ${direction.to}`;
28
+ }
29
+ /** A kind, as its label is coloured. */
30
+ const KIND_VARIANTS = {
31
+ request: 'accent',
32
+ 'status-update': 'attention',
33
+ 'artifact-update': 'success',
34
+ task: 'secondary',
35
+ message: 'done',
36
+ 'agent-card': 'secondary',
37
+ response: 'secondary',
38
+ 'tool-call': 'sponsors',
39
+ turn: 'primary',
40
+ error: 'danger',
41
+ approval: 'severe',
42
+ };
43
+ const pad = (value, length = 2) => String(value).padStart(length, '0');
44
+ /** A time of day, to the millisecond: `15:44:31.299`. */
45
+ export function clockTime(at) {
46
+ const date = new Date(at);
47
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`;
48
+ }
49
+ /** Time since the first entry: `+1.204s`. */
50
+ export function sinceFirst(at, first) {
51
+ return `+${((at - first) / 1000).toFixed(3)}s`;
52
+ }
53
+ /** How long a call took: `412 ms`, `3.4 s`. */
54
+ export function durationText(ms) {
55
+ return ms < 1000 ? `${Math.round(ms)} ms` : `${(ms / 1000).toFixed(1)} s`;
56
+ }
57
+ /** The entries a filter keeps: an empty set keeps every value. */
58
+ export function filterEntries(entries, filter) {
59
+ return entries.filter(entry => (!filter.sources.size || filter.sources.has(entry.source)) &&
60
+ (!filter.kinds.size || filter.kinds.has(entry.kind)) &&
61
+ (!filter.actors.size || filter.actors.has(entry.actor)));
62
+ }
63
+ /** A session, as JSON. */
64
+ export function exportEntries(entries) {
65
+ return { exportedAt: new Date().toISOString(), entries: [...entries] };
66
+ }
67
+ function toggle(set, value) {
68
+ const next = new Set(set);
69
+ if (next.has(value)) {
70
+ next.delete(value);
71
+ }
72
+ else {
73
+ next.add(value);
74
+ }
75
+ return next;
76
+ }
77
+ function FilterMenu({ label, options, selected, onToggle, onAll, name, }) {
78
+ return (_jsxs(ActionMenu, { children: [_jsxs(ActionMenu.Button, { size: "small", "data-inspector-filter": label.toLowerCase(), children: [label, selected.size ? ` · ${selected.size}` : ''] }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsxs(ActionList, { selectionVariant: "multiple", "aria-label": label, children: [_jsx(ActionList.Item, { selected: !selected.size, onSelect: onAll, children: "All" }), _jsx(ActionList.Divider, {}), options.map(option => (_jsx(ActionList.Item, { selected: selected.has(option), onSelect: () => onToggle(option), children: name(option) }, option)))] }) })] }));
79
+ }
80
+ function Payload({ title, value, large, }) {
81
+ const bytes = useMemo(() => {
82
+ try {
83
+ return new TextEncoder().encode(JSON.stringify(value) ?? '').length;
84
+ }
85
+ catch {
86
+ return 0;
87
+ }
88
+ }, [value]);
89
+ const [shown, setShown] = useState(bytes <= large);
90
+ return (_jsxs(Box, { sx: { minWidth: 0 }, "data-inspector-payload": title, children: [_jsxs(Text, { sx: {
91
+ fontSize: 0,
92
+ color: 'fg.muted',
93
+ fontWeight: 600,
94
+ display: 'block',
95
+ }, children: [title, " \u00B7 ", formatBytes(bytes)] }), shown ? (_jsx(JsonTree, { value: value, expandDepth: bytes > large ? 1 : 2 })) : (_jsxs(Button, { size: "small", onClick: () => setShown(true), "data-inspector-large": "", children: ["Show ", formatBytes(bytes)] }))] }));
96
+ }
97
+ function Row({ entry, first, open, onToggle, large, agent, }) {
98
+ const ended = entry.endedAt !== undefined;
99
+ return (_jsxs(Box, { as: "li", "data-inspector-entry": entry.id, "data-inspector-source": entry.source, "data-inspector-kind": entry.kind, "data-inspector-status": entry.status ?? '', sx: {
100
+ borderBottom: '1px solid',
101
+ borderColor: 'border.muted',
102
+ listStyle: 'none',
103
+ }, children: [_jsxs(Box, { as: "button", type: "button", "aria-expanded": open, onClick: onToggle, sx: {
104
+ width: '100%',
105
+ display: 'grid',
106
+ gridTemplateColumns: ['auto 1fr', 'auto auto auto minmax(0, 1fr)'],
107
+ columnGap: 2,
108
+ rowGap: 1,
109
+ alignItems: 'center',
110
+ px: 2,
111
+ py: 1,
112
+ border: 0,
113
+ bg: open ? 'canvas.subtle' : 'transparent',
114
+ color: 'fg.default',
115
+ font: 'inherit',
116
+ fontSize: 0,
117
+ textAlign: 'left',
118
+ cursor: 'pointer',
119
+ '&:hover': { bg: 'canvas.subtle' },
120
+ }, children: [_jsxs(Box, { sx: { fontFamily: 'mono', color: 'fg.muted', whiteSpace: 'nowrap' }, children: [_jsx("span", { title: new Date(entry.at).toISOString(), children: clockTime(entry.at) }), ' ', _jsx(Text, { sx: { opacity: 0.7 }, children: sinceFirst(entry.at, first) })] }), _jsx(Text, { sx: { whiteSpace: 'nowrap', fontWeight: 600 }, "data-inspector-direction": "", children: directionFrom(entry, agent) }), _jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [_jsx(Label, { size: "small", variant: "secondary", children: AGENT_INSPECTOR_SOURCE_LABELS[entry.source] }), _jsx(Label, { size: "small", variant: KIND_VARIANTS[entry.kind] ?? 'secondary', children: entry.kind })] }), _jsxs(Box, { sx: { display: 'flex', gap: 1, alignItems: 'center', minWidth: 0 }, children: [(entry.icon || entry.emoji) && (_jsx(SpecMark, { icon: entry.icon, emoji: entry.emoji, size: 14 })), entry.name && entry.kind !== 'request' && (_jsx(Text, { sx: { fontFamily: 'mono', fontWeight: 600, whiteSpace: 'nowrap' }, children: entry.name })), entry.status && (_jsxs(Label, { size: "small", variant: entry.status === 'failed'
121
+ ? 'danger'
122
+ : entry.status === 'running'
123
+ ? 'attention'
124
+ : 'success', children: [entry.status, ended && entry.endedAt !== undefined
125
+ ? ` · ${durationText(entry.endedAt - entry.at)}`
126
+ : ''] })), _jsx(Text, { sx: {
127
+ color: 'fg.muted',
128
+ overflow: 'hidden',
129
+ textOverflow: 'ellipsis',
130
+ whiteSpace: 'nowrap',
131
+ minWidth: 0,
132
+ }, title: entry.summary, children: entry.summary }), entry.bytes > large && (_jsx(Text, { sx: { color: 'fg.muted', whiteSpace: 'nowrap' }, children: formatBytes(entry.bytes) }))] })] }), open && (_jsxs(Box, { "data-inspector-details": "", sx: {
133
+ px: 3,
134
+ py: 2,
135
+ display: 'flex',
136
+ flexDirection: 'column',
137
+ gap: 2,
138
+ bg: 'canvas.subtle',
139
+ }, children: [entry.a2a && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', fontFamily: 'mono' }, children: [
140
+ entry.a2a.method && `method ${entry.a2a.method}`,
141
+ entry.a2a.rpcId !== undefined && `id ${entry.a2a.rpcId}`,
142
+ entry.a2a.taskId && `task ${entry.a2a.taskId}`,
143
+ entry.a2a.contextId && `context ${entry.a2a.contextId}`,
144
+ entry.a2a.state && entry.a2a.state,
145
+ entry.a2a.chunks &&
146
+ entry.a2a.chunks > 1 &&
147
+ `${entry.a2a.chunks} chunks`,
148
+ ]
149
+ .filter(Boolean)
150
+ .join(' · ') })), entry.error && (_jsx(Text, { sx: { color: 'danger.fg', fontSize: 0 }, children: entry.error })), entry.payload !== undefined && (_jsx(Payload, { title: entry.kind === 'tool-call' ? 'Arguments' : 'Payload', value: entry.payload, large: large })), entry.result !== undefined && (_jsx(Payload, { title: "Result", value: entry.result, large: large })), entry.events.length > 0 && (_jsx(Payload, { title: "Events", value: entry.events, large: large })), entry.links.length > 0 && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: ["Linked: ", entry.links.join(', ')] }))] }))] }));
151
+ }
152
+ /** Save a session as a JSON file. */
153
+ function download(name, session) {
154
+ const blob = new Blob([JSON.stringify(session, null, 2)], {
155
+ type: 'application/json',
156
+ });
157
+ const url = URL.createObjectURL(blob);
158
+ const link = document.createElement('a');
159
+ link.href = url;
160
+ link.download = `${name}.json`;
161
+ link.click();
162
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
163
+ }
164
+ /** The Agent Inspector. */
165
+ export function AgentInspector({ sink, entries: given, maxHeight = 420, largeBytes = 16_000, emptyText = 'Nothing recorded yet.', exportName = 'agent-inspector', agent, }) {
166
+ const live = useAgentInspectorEntries(sink);
167
+ const everything = given ?? live;
168
+ const all = useMemo(() => agent
169
+ ? everything.filter(entry => isAgentsEntry(entry, agent))
170
+ : everything, [everything, agent]);
171
+ const [held, setHeld] = useState(null);
172
+ const [clearedAt, setClearedAt] = useState(0);
173
+ const [sources, setSources] = useState(new Set());
174
+ const [kinds, setKinds] = useState(new Set());
175
+ const [actors, setActors] = useState(new Set());
176
+ const [open, setOpen] = useState(new Set());
177
+ const recorded = useMemo(() => (clearedAt ? all.filter(entry => entry.at >= clearedAt) : all), [all, clearedAt]);
178
+ const shown = held ?? recorded;
179
+ const waiting = held ? recorded.length - held.length : 0;
180
+ const kindOptions = useMemo(() => [...new Set(recorded.map(entry => entry.kind))].sort(), [recorded]);
181
+ const actorOptions = useMemo(() => [...new Set(recorded.map(entry => entry.actor))].sort(), [recorded]);
182
+ const sourceOptions = useMemo(() => AGENT_INSPECTOR_SOURCES.filter(source => recorded.some(entry => entry.source === source)), [recorded]);
183
+ const rows = useMemo(() => filterEntries(shown, { sources, kinds, actors }), [shown, sources, kinds, actors]);
184
+ const first = shown[0]?.at ?? 0;
185
+ return (_jsxs(Box, { "data-agent-inspector": "", sx: {
186
+ border: '1px solid',
187
+ borderColor: 'border.default',
188
+ borderRadius: 2,
189
+ bg: 'canvas.default',
190
+ color: 'fg.default',
191
+ minWidth: 0,
192
+ }, children: [_jsxs(Box, { role: "toolbar", "aria-label": agent ? `${agent}'s Agent Inspector` : 'Agent Inspector', sx: {
193
+ display: 'flex',
194
+ flexWrap: 'wrap',
195
+ gap: 2,
196
+ alignItems: 'center',
197
+ p: 2,
198
+ borderBottom: '1px solid',
199
+ borderColor: 'border.default',
200
+ }, children: [_jsx(FilterMenu, { label: "Source", options: sourceOptions, selected: sources, onToggle: value => setSources(toggle(sources, value)), onAll: () => setSources(new Set()), name: value => AGENT_INSPECTOR_SOURCE_LABELS[value] }), _jsx(FilterMenu, { label: "Kind", options: kindOptions, selected: kinds, onToggle: value => setKinds(toggle(kinds, value)), onAll: () => setKinds(new Set()), name: value => value }), _jsx(FilterMenu, { label: "Actor", options: actorOptions, selected: actors, onToggle: value => setActors(toggle(actors, value)), onAll: () => setActors(new Set()), name: value => value }), _jsx(Box, { sx: { flex: 1 } }), _jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, "data-inspector-count": "", children: [rows.length === shown.length
201
+ ? `${rows.length} ${rows.length === 1 ? 'entry' : 'entries'}`
202
+ : `${rows.length} of ${shown.length}`, waiting > 0 ? ` · ${waiting} new` : ''] }), _jsx(Button, { size: "small", leadingVisual: held ? PlayIcon : PauseIcon, "aria-pressed": held !== null, onClick: () => setHeld(held ? null : recorded), "data-inspector-pause": "", children: held ? 'Resume' : 'Pause' }), _jsx(Button, { size: "small", leadingVisual: TrashIcon, onClick: () => {
203
+ setOpen(new Set());
204
+ setHeld(null);
205
+ // One agent's view of a shared record clears only itself.
206
+ if (sink && !given && !agent) {
207
+ sink.clear();
208
+ }
209
+ else {
210
+ setClearedAt(Date.now());
211
+ }
212
+ }, "data-inspector-clear": "", children: "Clear" }), _jsx(Button, { size: "small", leadingVisual: DownloadIcon, disabled: !recorded.length, onClick: () => download(exportName, exportEntries(recorded)), "data-inspector-export": "", children: "Export" })] }), _jsx(Box, { as: "ol", "aria-label": "Recorded", sx: { m: 0, p: 0, maxHeight, overflowY: 'auto' }, children: rows.length === 0 ? (_jsx(Text, { as: "li", sx: {
213
+ display: 'block',
214
+ p: 3,
215
+ color: 'fg.muted',
216
+ fontSize: 1,
217
+ listStyle: 'none',
218
+ }, children: emptyText })) : (rows.map(entry => (_jsx(Row, { entry: entry, first: first, large: largeBytes, agent: agent, open: open.has(entry.id), onToggle: () => setOpen(toggle(open, entry.id)) }, entry.id)))) })] }));
219
+ }
220
+ export default AgentInspector;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The Agent Inspector over the page: a large dialog, the inspector loaded
3
+ * only when it opens, fed by the sink the agent's chat or team feeds. Esc
4
+ * closes it.
5
+ *
6
+ * @module components/inspector/AgentInspectorDialog
7
+ */
8
+ import type { JSX } from 'react';
9
+ import type { AgentInspectorSink } from './agentInspector';
10
+ export type AgentInspectorDialogProps = {
11
+ sink: AgentInspectorSink | null;
12
+ /** The dialog's title. */
13
+ title?: string;
14
+ /** One agent's own record (`AgentInspector`'s `agent`); all of it without. */
15
+ agent?: string;
16
+ onClose: () => void;
17
+ };
18
+ export declare function AgentInspectorDialog({ sink, title, agent, onClose, }: AgentInspectorDialogProps): JSX.Element;
19
+ export default AgentInspectorDialog;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Suspense, lazy } from 'react';
3
+ import { Dialog } from '@primer/react';
4
+ import { Text } from '@primer/react';
5
+ const AgentInspector = lazy(() => import('./AgentInspector'));
6
+ export function AgentInspectorDialog({ sink, title = 'Agent Inspector', agent, onClose, }) {
7
+ return (_jsx(Dialog, { title: title, onClose: onClose, width: "xlarge", height: "large", "data-agent-inspector-dialog": "", children: _jsx(Suspense, { fallback: _jsx(Text, { sx: { color: 'fg.muted' }, children: "Loading the Agent Inspector\u2026" }), children: _jsx(AgentInspector, { sink: sink, agent: agent, maxHeight: "calc(80vh - 140px)", emptyText: "Nothing recorded yet: what the agent does shows here as it does it." }) }) }));
8
+ }
9
+ export default AgentInspectorDialog;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A JSON value as a tree: objects and arrays fold and unfold, each value
3
+ * coloured by its type (theme colours, light and dark), long strings cut
4
+ * until asked for whole, and the whole value copied as JSON.
5
+ *
6
+ * @module components/inspector/JsonTree
7
+ */
8
+ import type { JSX } from 'react';
9
+ export type JsonTreeProps = {
10
+ value: unknown;
11
+ /** The root's name, shown as its key. */
12
+ name?: string;
13
+ /** How many levels open at first. */
14
+ expandDepth?: number;
15
+ /** Offer *Copy JSON* above the tree. */
16
+ copyable?: boolean;
17
+ /** Longer strings are cut until clicked. */
18
+ stringLimit?: number;
19
+ };
20
+ /** A value's type, as the tree colours it. */
21
+ export declare function jsonType(value: unknown): 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object' | 'undefined';
22
+ /** A JSON value, as a tree. */
23
+ export declare function JsonTree({ value, name, expandDepth, copyable, stringLimit, }: JsonTreeProps): JSX.Element;
24
+ export default JsonTree;
@@ -0,0 +1,89 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Button, Text } from '@primer/react';
4
+ import { Box } from '@datalayer/primer-addons';
5
+ import { CopyIcon, CheckIcon } from '@primer/octicons-react';
6
+ const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
7
+ /** A value's type, as the tree colours it. */
8
+ export function jsonType(value) {
9
+ if (value === null) {
10
+ return 'null';
11
+ }
12
+ if (Array.isArray(value)) {
13
+ return 'array';
14
+ }
15
+ const type = typeof value;
16
+ if (type === 'string' || type === 'number' || type === 'boolean') {
17
+ return type;
18
+ }
19
+ if (type === 'bigint') {
20
+ return 'number';
21
+ }
22
+ return type === 'undefined' ? 'undefined' : 'object';
23
+ }
24
+ const COLOURS = {
25
+ string: 'success.fg',
26
+ number: 'accent.fg',
27
+ boolean: 'done.fg',
28
+ null: 'fg.muted',
29
+ undefined: 'fg.muted',
30
+ };
31
+ function Leaf({ value, stringLimit, }) {
32
+ const type = jsonType(value);
33
+ const [whole, setWhole] = useState(false);
34
+ if (type === 'string') {
35
+ const text = value;
36
+ const cut = !whole && text.length > stringLimit;
37
+ return (_jsxs(Text, { "data-json-type": "string", sx: {
38
+ color: COLOURS.string,
39
+ whiteSpace: 'pre-wrap',
40
+ wordBreak: 'break-word',
41
+ }, children: [JSON.stringify(cut ? text.slice(0, stringLimit) : text), cut && (_jsxs(Box, { as: "button", type: "button", onClick: () => setWhole(true), sx: {
42
+ ml: 1,
43
+ p: 0,
44
+ border: 0,
45
+ bg: 'transparent',
46
+ color: 'accent.fg',
47
+ cursor: 'pointer',
48
+ font: 'inherit',
49
+ }, children: ["\u2026 ", text.length - stringLimit, " more"] }))] }));
50
+ }
51
+ return (_jsx(Text, { "data-json-type": type, sx: { color: COLOURS[type] ?? 'fg.default' }, children: type === 'undefined' ? 'undefined' : String(value) }));
52
+ }
53
+ function Node({ name, value, depth, expandDepth, stringLimit, }) {
54
+ const type = jsonType(value);
55
+ const branch = type === 'array' || type === 'object';
56
+ const [open, setOpen] = useState(depth < expandDepth);
57
+ const key = name !== undefined ? (_jsxs(Text, { sx: { color: 'fg.default', fontWeight: 600 }, children: [name, ": "] })) : null;
58
+ if (!branch) {
59
+ return (_jsxs(Box, { role: "treeitem", "data-json-node": "", sx: { pl: 3 }, children: [key, _jsx(Leaf, { value: value, stringLimit: stringLimit })] }));
60
+ }
61
+ const items = type === 'array'
62
+ ? value.map((item, index) => [String(index), item])
63
+ : Object.entries(value);
64
+ const [openMark, closeMark] = type === 'array' ? ['[', ']'] : ['{', '}'];
65
+ return (_jsxs(Box, { role: "treeitem", "aria-expanded": open, "data-json-node": "", sx: { pl: depth ? 3 : 0 }, children: [_jsxs(Box, { as: "button", type: "button", onClick: () => setOpen(!open), "aria-label": `${open ? 'Fold' : 'Unfold'} ${name ?? 'the value'}`, "data-json-toggle": "", sx: {
66
+ p: 0,
67
+ border: 0,
68
+ bg: 'transparent',
69
+ color: 'inherit',
70
+ font: 'inherit',
71
+ cursor: 'pointer',
72
+ textAlign: 'left',
73
+ }, children: [_jsx(Text, { sx: { color: 'fg.muted', display: 'inline-block', width: '1em' }, children: open ? '▾' : '▸' }), key, _jsx(Text, { sx: { color: 'fg.muted' }, children: open
74
+ ? openMark
75
+ : `${openMark} ${items.length} ${type === 'array' ? (items.length === 1 ? 'item' : 'items') : items.length === 1 ? 'key' : 'keys'} ${closeMark}` })] }), open && (_jsxs(Box, { role: "group", children: [items.map(([itemName, item]) => (_jsx(Node, { name: itemName, value: item, depth: depth + 1, expandDepth: expandDepth, stringLimit: stringLimit }, itemName))), _jsx(Text, { sx: { color: 'fg.muted', pl: 3 }, children: closeMark })] }))] }));
76
+ }
77
+ /** A JSON value, as a tree. */
78
+ export function JsonTree({ value, name, expandDepth = 1, copyable = true, stringLimit = 240, }) {
79
+ const [copied, setCopied] = useState(false);
80
+ const copy = () => {
81
+ const text = JSON.stringify(value, null, 2) ?? 'undefined';
82
+ void navigator.clipboard?.writeText(text).then(() => {
83
+ setCopied(true);
84
+ setTimeout(() => setCopied(false), 1500);
85
+ }, () => undefined);
86
+ };
87
+ return (_jsxs(Box, { "data-json-tree": "", sx: { fontFamily: MONO, fontSize: 0, lineHeight: 1.6, minWidth: 0 }, children: [copyable && (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: copied ? CheckIcon : CopyIcon, onClick: copy, "data-json-copy": "", sx: { float: 'right' }, children: copied ? 'Copied' : 'Copy JSON' })), _jsx(Box, { role: "tree", "aria-label": name ?? 'JSON', children: _jsx(Node, { name: name, value: value, depth: 0, expandDepth: expandDepth, stringLimit: stringLimit }) })] }));
88
+ }
89
+ export default JsonTree;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The A2A traffic between two agents, as the Agent Inspector records it: a
3
+ * fetch that hands every request on and records what went (the JSON-RPC
4
+ * request: its method, its message) and what came back — a JSON-RPC
5
+ * response, or each server-sent event of a stream (the task, its status
6
+ * updates, its artifact updates, a message) — with its task, context and
7
+ * state, whole.
8
+ *
9
+ * It sits under the A2A SDK (`ClientFactory`'s `JsonRpcTransportFactory`
10
+ * takes a `fetchImpl`): the SDK reads the response it is handed, and the
11
+ * inspector reads a copy of its body. Nothing it records changes what the
12
+ * SDK sees, and a key sent as a header is never recorded.
13
+ *
14
+ * A peer served by agent-runtimes tells each tool call of its agent on a
15
+ * `working` status (`{tool_call}`, then `{tool_result}`): each call is
16
+ * recorded besides as one entry of its own, the peer's, from its start to
17
+ * its end, linked to the two statuses — an MCP call, a skill, or a tool its
18
+ * runtime runs, by who claims its name. The text chunks of one artifact are
19
+ * one entry, their count kept.
20
+ *
21
+ * {@link linkA2APeerEvent} attaches what the asker made of an event (an
22
+ * `A2APeerEvent`) to the entry it came from.
23
+ *
24
+ * @module components/inspector/a2aInspect
25
+ */
26
+ import type { A2APeerEvent } from '../../runtimes/browser/a2aPeer';
27
+ import { type AgentInspectorEntry, type AgentInspectorSink, type ToolClass } from './agentInspector';
28
+ export type A2AInspectOptions = {
29
+ sink: AgentInspectorSink;
30
+ /** The agent that asks: `Sales`. */
31
+ asker: string;
32
+ /** The agent asked: `Accounting`. */
33
+ peer: string;
34
+ /**
35
+ * Where a peer's tool call comes from — an MCP server's, a skill, … — and
36
+ * its mark (`classifyToolCall`, or a team's `teamToolClassifier`).
37
+ * Without it, a tool the peer's runtime runs (`backend-tool`).
38
+ */
39
+ classifyTool?: (name: string, args?: Record<string, unknown>) => ToolClass;
40
+ };
41
+ /** The text of an A2A 1.0 message's or artifact's parts, as JSON carries them. */
42
+ export declare function partsText(parts: unknown): string;
43
+ /** `TASK_STATE_WORKING` is `working`. */
44
+ export declare function stateWord(state: unknown): string;
45
+ /**
46
+ * A fetch that records the A2A traffic it carries into `options.sink`, and
47
+ * hands the SDK what the network gave.
48
+ */
49
+ export declare function inspectA2AFetch(base: typeof globalThis.fetch, options: A2AInspectOptions): typeof globalThis.fetch;
50
+ /**
51
+ * The linker the A2A capture adds to its sink: an `A2APeerEvent` noted
52
+ * (`sink.note`) is attached to the entry it came from.
53
+ */
54
+ export declare function a2aEventLinker(event: unknown): ((entry: AgentInspectorEntry) => boolean) | undefined;
55
+ /** Attach what the asker made of an event to the A2A entry it came from. */
56
+ export declare function linkA2APeerEvent(sink: AgentInspectorSink, event: A2APeerEvent): void;