@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.
- package/lib/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +27 -1
- package/lib/chat/ChatFloating.js +97 -13
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
- package/lib/chat/assistant/AssistantContextMenu.js +68 -0
- package/lib/chat/assistant/AssistantStage.d.ts +144 -4
- package/lib/chat/assistant/AssistantStage.js +280 -10
- package/lib/chat/assistant/BalloonParts.d.ts +33 -5
- package/lib/chat/assistant/BalloonParts.js +66 -44
- package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
- package/lib/chat/assistant/BalloonVisual.js +4 -3
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +54 -6
- package/lib/chat/assistant/SpeechBalloon.js +73 -22
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +71 -1
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/indicators/TypingDots.d.ts +13 -0
- package/lib/chat/indicators/TypingDots.js +21 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +25 -33
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- 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 +69 -1
- package/lib/components/teams/A2ATeamGraph.js +277 -37
- package/lib/components/teams/NotebookPreviewView.js +9 -1
- package/lib/components/teams/TeamNotebook.d.ts +3 -1
- package/lib/components/teams/TeamNotebook.js +3 -3
- package/lib/components/teams/TeamNotebookView.js +15 -0
- 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/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +20 -3
- package/lib/components/teams/useA2ATeam.js +108 -23
- package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
- package/lib/examples/AgentA2ATeamExample.js +67 -13
- package/lib/examples/AssistantExample.js +65 -26
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +22 -4
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/core/index.d.ts +17 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +58 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +12 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Agent Inspector's record: what an agent does, as it does it, one entry
|
|
7
|
+
* a thing — an A2A request or event, an MCP tool call, a skill, a frontend or
|
|
8
|
+
* backend tool, a model turn, an approval — each with its time, the agent
|
|
9
|
+
* that did it (`actor`), where it came from (`source`), its kind, its name,
|
|
10
|
+
* its status and, once it ends, its duration, a line that says it, and its
|
|
11
|
+
* payload whole.
|
|
12
|
+
*
|
|
13
|
+
* A call's start and its end are one entry: {@link AgentInspectorSink.start}
|
|
14
|
+
* opens it under a key (a tool call's id), {@link AgentInspectorSink.end}
|
|
15
|
+
* closes it with its result. Anything can push into a sink: the A2A capture
|
|
16
|
+
* (`inspectA2AFetch`), a team (`useA2ATeam`'s `inspector`), a chat
|
|
17
|
+
* (`ChatBase`, under {@link AgentInspectorProvider}). `AgentInspector` draws
|
|
18
|
+
* it.
|
|
19
|
+
*
|
|
20
|
+
* @module components/inspector/agentInspector
|
|
21
|
+
*/
|
|
22
|
+
import { createContext, createElement, useContext, useState, useSyncExternalStore, } from 'react';
|
|
23
|
+
/** Every source, in the order the filters list them. */
|
|
24
|
+
export const AGENT_INSPECTOR_SOURCES = [
|
|
25
|
+
'a2a',
|
|
26
|
+
'mcp',
|
|
27
|
+
'skill',
|
|
28
|
+
'frontend-tool',
|
|
29
|
+
'backend-tool',
|
|
30
|
+
'model',
|
|
31
|
+
'approval',
|
|
32
|
+
];
|
|
33
|
+
/** A source, in words. */
|
|
34
|
+
export const AGENT_INSPECTOR_SOURCE_LABELS = {
|
|
35
|
+
a2a: 'A2A',
|
|
36
|
+
mcp: 'MCP',
|
|
37
|
+
skill: 'Skill',
|
|
38
|
+
'frontend-tool': 'Frontend tool',
|
|
39
|
+
'backend-tool': 'Backend tool',
|
|
40
|
+
model: 'Model',
|
|
41
|
+
approval: 'Approval',
|
|
42
|
+
};
|
|
43
|
+
/** How large a value is, as JSON, in bytes. */
|
|
44
|
+
export function jsonBytes(value) {
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const text = JSON.stringify(value);
|
|
50
|
+
return text === undefined ? 0 : new TextEncoder().encode(text).length;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** A size, in words: `812 B`, `12.3 kB`, `1.2 MB`. */
|
|
57
|
+
export function formatBytes(bytes) {
|
|
58
|
+
if (bytes < 1000) {
|
|
59
|
+
return `${bytes} B`;
|
|
60
|
+
}
|
|
61
|
+
if (bytes < 1_000_000) {
|
|
62
|
+
return `${(bytes / 1000).toFixed(1)} kB`;
|
|
63
|
+
}
|
|
64
|
+
return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
65
|
+
}
|
|
66
|
+
/** A line short enough for a row. */
|
|
67
|
+
export function shortLine(text, length = 160) {
|
|
68
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
69
|
+
return flat.length > length ? `${flat.slice(0, length - 1)}…` : flat;
|
|
70
|
+
}
|
|
71
|
+
/** How many semantic events wait for their entry. */
|
|
72
|
+
const PENDING_LIMIT = 32;
|
|
73
|
+
/** Make a sink: the record, and who listens to it. */
|
|
74
|
+
export function createAgentInspector(options = {}) {
|
|
75
|
+
const now = options.now ?? Date.now;
|
|
76
|
+
const limit = options.limit ?? 2000;
|
|
77
|
+
let list = [];
|
|
78
|
+
let counter = 0;
|
|
79
|
+
const open = new Map();
|
|
80
|
+
const listeners = new Set();
|
|
81
|
+
let pending = [];
|
|
82
|
+
let scheduled = false;
|
|
83
|
+
const linkers = new Set();
|
|
84
|
+
const notify = () => {
|
|
85
|
+
if (scheduled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
scheduled = true;
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
scheduled = false;
|
|
91
|
+
listeners.forEach(listener => listener());
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const replace = (id, next) => {
|
|
95
|
+
list = list.map(entry => (entry.id === id ? next : entry));
|
|
96
|
+
};
|
|
97
|
+
const settlePending = (entry) => {
|
|
98
|
+
if (!pending.length) {
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
const taken = pending.filter(waiting => waiting.match(entry));
|
|
102
|
+
if (!taken.length) {
|
|
103
|
+
return entry;
|
|
104
|
+
}
|
|
105
|
+
pending = pending.filter(waiting => !taken.includes(waiting));
|
|
106
|
+
return {
|
|
107
|
+
...entry,
|
|
108
|
+
events: [...entry.events, ...taken.map(waiting => waiting.event)],
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
const push = (input) => {
|
|
112
|
+
counter += 1;
|
|
113
|
+
const id = `e${counter}`;
|
|
114
|
+
const { links, at, ...rest } = input;
|
|
115
|
+
const entry = settlePending({
|
|
116
|
+
...rest,
|
|
117
|
+
id,
|
|
118
|
+
at: at ?? now(),
|
|
119
|
+
bytes: jsonBytes(input.payload) + jsonBytes(input.result),
|
|
120
|
+
links: links ?? [],
|
|
121
|
+
events: [],
|
|
122
|
+
});
|
|
123
|
+
list = [...list, entry];
|
|
124
|
+
if (list.length > limit) {
|
|
125
|
+
list = list.slice(list.length - limit);
|
|
126
|
+
}
|
|
127
|
+
notify();
|
|
128
|
+
return id;
|
|
129
|
+
};
|
|
130
|
+
const find = (id) => list.find(entry => entry.id === id);
|
|
131
|
+
const sink = {
|
|
132
|
+
push,
|
|
133
|
+
start(input) {
|
|
134
|
+
const known = open.get(input.key);
|
|
135
|
+
const entry = known ? find(known) : undefined;
|
|
136
|
+
if (entry) {
|
|
137
|
+
const payload = input.payload ?? entry.payload;
|
|
138
|
+
replace(entry.id, {
|
|
139
|
+
...entry,
|
|
140
|
+
payload,
|
|
141
|
+
summary: input.summary || entry.summary,
|
|
142
|
+
links: [...entry.links, ...(input.links ?? [])],
|
|
143
|
+
bytes: jsonBytes(payload) + jsonBytes(entry.result),
|
|
144
|
+
});
|
|
145
|
+
notify();
|
|
146
|
+
return entry.id;
|
|
147
|
+
}
|
|
148
|
+
const id = push({ ...input, status: 'running' });
|
|
149
|
+
open.set(input.key, id);
|
|
150
|
+
return id;
|
|
151
|
+
},
|
|
152
|
+
end(key, outcome = {}) {
|
|
153
|
+
const id = open.get(key);
|
|
154
|
+
const entry = id ? find(id) : undefined;
|
|
155
|
+
if (!id || !entry) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
open.delete(key);
|
|
159
|
+
const result = outcome.result ?? entry.result;
|
|
160
|
+
replace(id, {
|
|
161
|
+
...entry,
|
|
162
|
+
status: outcome.status ?? (outcome.error ? 'failed' : 'done'),
|
|
163
|
+
endedAt: outcome.at ?? now(),
|
|
164
|
+
result,
|
|
165
|
+
error: outcome.error ?? entry.error,
|
|
166
|
+
summary: outcome.summary ?? entry.summary,
|
|
167
|
+
links: [...entry.links, ...(outcome.links ?? [])],
|
|
168
|
+
bytes: jsonBytes(entry.payload) + jsonBytes(result),
|
|
169
|
+
});
|
|
170
|
+
notify();
|
|
171
|
+
return id;
|
|
172
|
+
},
|
|
173
|
+
update(id, patch) {
|
|
174
|
+
const entry = find(id);
|
|
175
|
+
if (!entry) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const next = { ...entry, ...patch(entry) };
|
|
179
|
+
replace(id, {
|
|
180
|
+
...next,
|
|
181
|
+
bytes: jsonBytes(next.payload) + jsonBytes(next.result),
|
|
182
|
+
});
|
|
183
|
+
notify();
|
|
184
|
+
},
|
|
185
|
+
attach(match, event) {
|
|
186
|
+
for (let index = list.length - 1; index >= 0; index -= 1) {
|
|
187
|
+
const entry = list[index];
|
|
188
|
+
if (match(entry) && !entry.events.includes(event)) {
|
|
189
|
+
replace(entry.id, { ...entry, events: [...entry.events, event] });
|
|
190
|
+
notify();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
pending = [...pending, { match, event }].slice(-PENDING_LIMIT);
|
|
195
|
+
},
|
|
196
|
+
running(key) {
|
|
197
|
+
return open.has(key);
|
|
198
|
+
},
|
|
199
|
+
note(event) {
|
|
200
|
+
for (const linker of linkers) {
|
|
201
|
+
const match = linker(event);
|
|
202
|
+
if (match) {
|
|
203
|
+
sink.attach(match, event);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
addLinker(linker) {
|
|
209
|
+
linkers.add(linker);
|
|
210
|
+
return () => {
|
|
211
|
+
linkers.delete(linker);
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
clear() {
|
|
215
|
+
list = [];
|
|
216
|
+
open.clear();
|
|
217
|
+
pending = [];
|
|
218
|
+
notify();
|
|
219
|
+
},
|
|
220
|
+
entries() {
|
|
221
|
+
return list;
|
|
222
|
+
},
|
|
223
|
+
subscribe(listener) {
|
|
224
|
+
listeners.add(listener);
|
|
225
|
+
return () => {
|
|
226
|
+
listeners.delete(listener);
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
exportSession() {
|
|
230
|
+
return { exportedAt: new Date(now()).toISOString(), entries: [...list] };
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
return sink;
|
|
234
|
+
}
|
|
235
|
+
const NO_ENTRIES = [];
|
|
236
|
+
const NO_SUBSCRIPTION = () => () => undefined;
|
|
237
|
+
/** A sink's entries, kept current; none without a sink. */
|
|
238
|
+
export function useAgentInspectorEntries(sink) {
|
|
239
|
+
return useSyncExternalStore(sink ? sink.subscribe : NO_SUBSCRIPTION, () => (sink ? sink.entries() : NO_ENTRIES), () => (sink ? sink.entries() : NO_ENTRIES));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* A sink for a page, made once (or the one given), and its entries: what a
|
|
243
|
+
* page passes to its team, its chat and its `AgentInspector`.
|
|
244
|
+
*/
|
|
245
|
+
export function useAgentInspector(given) {
|
|
246
|
+
const [own] = useState(() => given ?? createAgentInspector());
|
|
247
|
+
const sink = given ?? own;
|
|
248
|
+
return { sink, entries: useAgentInspectorEntries(sink) };
|
|
249
|
+
}
|
|
250
|
+
/** The sink the chats under it push into. */
|
|
251
|
+
export const AgentInspectorContext = createContext(null);
|
|
252
|
+
/** The sink of the nearest {@link AgentInspectorProvider}, if any. */
|
|
253
|
+
export function useAgentInspectorSink() {
|
|
254
|
+
return useContext(AgentInspectorContext);
|
|
255
|
+
}
|
|
256
|
+
/** Every chat under it records what its agent does into `sink`. */
|
|
257
|
+
export function AgentInspectorProvider({ sink, children, }) {
|
|
258
|
+
return createElement(AgentInspectorContext.Provider, { value: sink }, children);
|
|
259
|
+
}
|
|
260
|
+
/** A call's arguments, in a few words: `kind=customer, limit=100`. */
|
|
261
|
+
export function argumentsLine(args, length = 100) {
|
|
262
|
+
if (!args || typeof args !== 'object') {
|
|
263
|
+
return args === undefined ? '' : shortLine(String(args), length);
|
|
264
|
+
}
|
|
265
|
+
const parts = Object.entries(args).map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`);
|
|
266
|
+
return shortLine(parts.join(', '), length);
|
|
267
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A chat's agent, as the Agent Inspector records it: each turn of its model
|
|
3
|
+
* (the prompt, the answer, the tokens), and each tool call from its start to
|
|
4
|
+
* its end — an MCP server's, a skill, a frontend tool the page runs, a tool
|
|
5
|
+
* the runtime runs — whatever transport the chat speaks (AG-UI, Vercel AI,
|
|
6
|
+
* A2A, the in-browser harness): `ChatBase` tells it what its adapter told.
|
|
7
|
+
*
|
|
8
|
+
* @module components/inspector/chatInspect
|
|
9
|
+
*/
|
|
10
|
+
import type { MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
11
|
+
import { type AgentInspectorSink } from './agentInspector';
|
|
12
|
+
export type ChatInspectorRecorder = {
|
|
13
|
+
turnStarted: (prompt: string, model?: string) => void;
|
|
14
|
+
turnEnded: (outcome: {
|
|
15
|
+
text?: string;
|
|
16
|
+
usage?: {
|
|
17
|
+
promptTokens?: number;
|
|
18
|
+
completionTokens?: number;
|
|
19
|
+
totalTokens?: number;
|
|
20
|
+
};
|
|
21
|
+
error?: string;
|
|
22
|
+
}) => void;
|
|
23
|
+
toolStarted: (call: {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
args: Record<string, unknown>;
|
|
27
|
+
}) => void;
|
|
28
|
+
toolEnded: (id: string, outcome: {
|
|
29
|
+
result?: unknown;
|
|
30
|
+
error?: string;
|
|
31
|
+
}) => void;
|
|
32
|
+
};
|
|
33
|
+
/** A recorder for one chat's agent (`actor`) into `sink`. */
|
|
34
|
+
export declare function chatInspectorRecorder(sink: AgentInspectorSink, actor: () => string, options?: {
|
|
35
|
+
/** The page's own tools, by name: frontend tools. */
|
|
36
|
+
frontendTools?: () => readonly string[];
|
|
37
|
+
mcpServers?: () => readonly MarkedMcpServer[];
|
|
38
|
+
}): ChatInspectorRecorder;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { argumentsLine, shortLine, } from './agentInspector';
|
|
6
|
+
import { classifyToolCall } from './classify';
|
|
7
|
+
/** A recorder for one chat's agent (`actor`) into `sink`. */
|
|
8
|
+
export function chatInspectorRecorder(sink, actor, options = {}) {
|
|
9
|
+
let turns = 0;
|
|
10
|
+
let turnKey;
|
|
11
|
+
return {
|
|
12
|
+
turnStarted(prompt, model) {
|
|
13
|
+
turns += 1;
|
|
14
|
+
turnKey = `chat-turn:${turns}`;
|
|
15
|
+
sink.start({
|
|
16
|
+
key: turnKey,
|
|
17
|
+
actor: actor(),
|
|
18
|
+
source: 'model',
|
|
19
|
+
kind: 'turn',
|
|
20
|
+
...(model ? { name: model } : {}),
|
|
21
|
+
summary: shortLine(prompt),
|
|
22
|
+
payload: { prompt, ...(model ? { model } : {}) },
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
turnEnded({ text, usage, error }) {
|
|
26
|
+
if (!turnKey) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const total = usage?.totalTokens ??
|
|
30
|
+
(usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0);
|
|
31
|
+
sink.end(turnKey, {
|
|
32
|
+
...(error ? { status: 'failed', error } : {}),
|
|
33
|
+
result: {
|
|
34
|
+
...(text ? { text } : {}),
|
|
35
|
+
...(usage ? { usage } : {}),
|
|
36
|
+
},
|
|
37
|
+
...(total ? { summary: `${total} tokens` } : {}),
|
|
38
|
+
});
|
|
39
|
+
turnKey = undefined;
|
|
40
|
+
},
|
|
41
|
+
toolStarted({ id, name, args }) {
|
|
42
|
+
const kind = classifyToolCall(name, args, {
|
|
43
|
+
frontendTools: options.frontendTools?.(),
|
|
44
|
+
mcpServers: options.mcpServers?.(),
|
|
45
|
+
});
|
|
46
|
+
sink.start({
|
|
47
|
+
key: `chat-tool:${id}`,
|
|
48
|
+
actor: actor(),
|
|
49
|
+
source: kind.source,
|
|
50
|
+
...(kind.icon ? { icon: kind.icon } : {}),
|
|
51
|
+
...(kind.emoji ? { emoji: kind.emoji } : {}),
|
|
52
|
+
kind: 'tool-call',
|
|
53
|
+
name,
|
|
54
|
+
summary: argumentsLine(args),
|
|
55
|
+
payload: args,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
toolEnded(id, { result, error }) {
|
|
59
|
+
sink.end(`chat-tool:${id}`, {
|
|
60
|
+
...(error ? { status: 'failed', error } : {}),
|
|
61
|
+
result,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a tool call comes from, for the Agent Inspector, by what the
|
|
3
|
+
* catalogue says of its name. Apart from the record itself, which a page can
|
|
4
|
+
* keep without loading the catalogues.
|
|
5
|
+
*
|
|
6
|
+
* @module components/inspector/classify
|
|
7
|
+
*/
|
|
8
|
+
import { type MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
9
|
+
import type { AgentInspectorSource, ToolClass } from './agentInspector';
|
|
10
|
+
/**
|
|
11
|
+
* Where a tool call comes from, by who claims its name: a skill, one of
|
|
12
|
+
* `mcpServers`, a frontend tool (the catalogue's, or one of `frontendTools`),
|
|
13
|
+
* a runtime tool; otherwise `fallback` — a tool the runtime runs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function classifyToolCall(name: string, args?: Record<string, unknown>, options?: {
|
|
16
|
+
mcpServers?: readonly MarkedMcpServer[];
|
|
17
|
+
frontendTools?: readonly string[];
|
|
18
|
+
fallback?: AgentInspectorSource;
|
|
19
|
+
}): ToolClass;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Where a tool call comes from, for the Agent Inspector, by what the
|
|
7
|
+
* catalogue says of its name. Apart from the record itself, which a page can
|
|
8
|
+
* keep without loading the catalogues.
|
|
9
|
+
*
|
|
10
|
+
* @module components/inspector/classify
|
|
11
|
+
*/
|
|
12
|
+
import { marksOfToolCall, } from '../../chat/marks/toolMarks';
|
|
13
|
+
/**
|
|
14
|
+
* Where a tool call comes from, by who claims its name: a skill, one of
|
|
15
|
+
* `mcpServers`, a frontend tool (the catalogue's, or one of `frontendTools`),
|
|
16
|
+
* a runtime tool; otherwise `fallback` — a tool the runtime runs.
|
|
17
|
+
*/
|
|
18
|
+
export function classifyToolCall(name, args, options = {}) {
|
|
19
|
+
if (options.frontendTools?.includes(name)) {
|
|
20
|
+
const marks = marksOfToolCall(name, args, options.mcpServers);
|
|
21
|
+
return {
|
|
22
|
+
source: 'frontend-tool',
|
|
23
|
+
...(marks?.icon ? { icon: marks.icon } : {}),
|
|
24
|
+
...(marks?.emoji ? { emoji: marks.emoji } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const marks = marksOfToolCall(name, args, options.mcpServers);
|
|
28
|
+
const source = marks?.kind === 'skill'
|
|
29
|
+
? 'skill'
|
|
30
|
+
: marks?.kind === 'mcp'
|
|
31
|
+
? 'mcp'
|
|
32
|
+
: marks?.kind === 'frontend'
|
|
33
|
+
? 'frontend-tool'
|
|
34
|
+
: marks?.kind === 'runtime'
|
|
35
|
+
? 'backend-tool'
|
|
36
|
+
: (options.fallback ?? 'backend-tool');
|
|
37
|
+
return {
|
|
38
|
+
source,
|
|
39
|
+
...(marks?.icon ? { icon: marks.icon } : {}),
|
|
40
|
+
...(marks?.emoji ? { emoji: marks.emoji } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Agent Inspector: what agents do — A2A traffic, MCP calls, skills,
|
|
3
|
+
* frontend and backend tools, model turns, approvals — recorded into a sink
|
|
4
|
+
* and drawn as rows that unfold to their payloads.
|
|
5
|
+
*
|
|
6
|
+
* @module components/inspector
|
|
7
|
+
*/
|
|
8
|
+
export * from './agentInspector';
|
|
9
|
+
export * from './classify';
|
|
10
|
+
export * from './chatInspect';
|
|
11
|
+
export * from './a2aInspect';
|
|
12
|
+
export * from './JsonTree';
|
|
13
|
+
export * from './AgentInspector';
|
|
14
|
+
export * from './AgentInspectorDialog';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The Agent Inspector: what agents do — A2A traffic, MCP calls, skills,
|
|
7
|
+
* frontend and backend tools, model turns, approvals — recorded into a sink
|
|
8
|
+
* and drawn as rows that unfold to their payloads.
|
|
9
|
+
*
|
|
10
|
+
* @module components/inspector
|
|
11
|
+
*/
|
|
12
|
+
export * from './agentInspector';
|
|
13
|
+
export * from './classify';
|
|
14
|
+
export * from './chatInspect';
|
|
15
|
+
export * from './a2aInspect';
|
|
16
|
+
export * from './JsonTree';
|
|
17
|
+
export * from './AgentInspector';
|
|
18
|
+
export * from './AgentInspectorDialog';
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
import type { JSX } from 'react';
|
|
26
26
|
import { type Edge, type EdgeProps, type Viewport } from '@xyflow/react';
|
|
27
27
|
import '@xyflow/react/dist/base.css';
|
|
28
|
+
import { type AssistantAbout } from '../../chat/assistant/AssistantStage';
|
|
29
|
+
import type { BalloonSuggestion } from '../../chat/assistant/SpeechBalloon';
|
|
30
|
+
import type { DisplayItem } from '../../types/chat';
|
|
31
|
+
import type { AssistantMenuItem } from '../../chat/assistant/AssistantContextMenu';
|
|
32
|
+
import type { AssistantSandbox } from '../../chat/assistant/assistantDetails';
|
|
33
|
+
import type { AgentInspectorSink } from '../inspector/agentInspector';
|
|
28
34
|
import { type A2ATeamCall, type A2ATeamConnection, type A2ATeamFlow } from './a2aTeamFlow';
|
|
29
35
|
import type { A2ATeamPersona } from './useA2ATeam';
|
|
30
36
|
import type { BalloonExpandTarget } from '../../chat/assistant/BalloonVisual';
|
|
@@ -65,6 +71,42 @@ export type A2ATeamGraphMember = {
|
|
|
65
71
|
expandTarget?: BalloonExpandTarget;
|
|
66
72
|
/** What its notebook is called: `Accounting's notebook`. */
|
|
67
73
|
notebookTitle?: string;
|
|
74
|
+
/**
|
|
75
|
+
* What the person may ask it: chips in its balloon while it waits for a
|
|
76
|
+
* question, and a group of its menu; one chosen is sent (`onSuggestion`).
|
|
77
|
+
*/
|
|
78
|
+
suggestions?: readonly BalloonSuggestion[];
|
|
79
|
+
onSuggestion?: (suggestion: BalloonSuggestion) => void;
|
|
80
|
+
/**
|
|
81
|
+
* The team's Agent Inspector: its menu offers *Inspect the agent…*, its
|
|
82
|
+
* own record (what it did, and the A2A messages it sent or received).
|
|
83
|
+
*/
|
|
84
|
+
inspector?: AgentInspectorSink | null;
|
|
85
|
+
/** Stops its turn, from its menu, while it works. */
|
|
86
|
+
onStop?: () => void;
|
|
87
|
+
/** What its menu's *About* and *Agent Details…* say. */
|
|
88
|
+
about?: AssistantAbout;
|
|
89
|
+
/**
|
|
90
|
+
* Its code sandbox, for *Code Sandbox Details…*. The entry — the agent in
|
|
91
|
+
* the page — has the kernel of the notebook open under the graph, while
|
|
92
|
+
* one is.
|
|
93
|
+
*/
|
|
94
|
+
sandbox?: AssistantSandbox;
|
|
95
|
+
/** The host's own entries of its menu. */
|
|
96
|
+
contextMenu?: readonly AssistantMenuItem[];
|
|
97
|
+
/**
|
|
98
|
+
* What its menu calls a click on it (`onToggle`): `Ask Sales` where it
|
|
99
|
+
* takes the person to the composer. Without `onToggle`, its menu has no
|
|
100
|
+
* such entry.
|
|
101
|
+
*/
|
|
102
|
+
conversationLabel?: string;
|
|
103
|
+
/**
|
|
104
|
+
* What it said and did, as its `history` balloon lists it (`useA2ATeam`'s
|
|
105
|
+
* `entryHistory`, `peerHistory`): shown when its menu chooses *History*.
|
|
106
|
+
*/
|
|
107
|
+
history?: readonly DisplayItem[];
|
|
108
|
+
/** Its balloon's display was chosen in its menu: the page may keep it. */
|
|
109
|
+
onBalloonDisplayChange?: (display: BalloonDisplay) => void;
|
|
68
110
|
};
|
|
69
111
|
export type A2ATeamGraphProps = {
|
|
70
112
|
/** The member that asks: drawn on the left. */
|
|
@@ -81,7 +123,33 @@ export type A2ATeamGraphProps = {
|
|
|
81
123
|
size?: number;
|
|
82
124
|
/** The tool calls running now, to the members' connections (`useA2ATeam`). */
|
|
83
125
|
calls?: A2ATeamCall[];
|
|
126
|
+
/**
|
|
127
|
+
* The room above the members for their balloons, in pixels at full scale:
|
|
128
|
+
* a page that shows suggestions, *more* and a notebook in them gives more.
|
|
129
|
+
* A notebook in a balloon gets at least its own.
|
|
130
|
+
*/
|
|
131
|
+
balloonRoom?: number;
|
|
132
|
+
};
|
|
133
|
+
/** How far a member was moved from its place, in the graph's coordinates. */
|
|
134
|
+
export type MemberOffset = {
|
|
135
|
+
dx: number;
|
|
136
|
+
dy: number;
|
|
137
|
+
};
|
|
138
|
+
/** How far a member may move: its node stays inside the graph's box. */
|
|
139
|
+
export type MemberBounds = {
|
|
140
|
+
minDx: number;
|
|
141
|
+
maxDx: number;
|
|
142
|
+
minDy: number;
|
|
143
|
+
maxDy: number;
|
|
84
144
|
};
|
|
145
|
+
/** An offset kept inside its bounds. */
|
|
146
|
+
export declare function clampOffset(offset: MemberOffset, bounds: MemberBounds): MemberOffset;
|
|
147
|
+
/** A member's accessible name: its name, and where it runs. */
|
|
148
|
+
export declare function memberLabel(member: Pick<A2ATeamGraphMember, 'name' | 'where'>): string;
|
|
149
|
+
/** A connection's accessible name: `Odoo, Accounting's MCP connection`. */
|
|
150
|
+
export declare function connectionLabel(connection: Pick<A2ATeamConnection, 'label'>, member: string | undefined): string;
|
|
151
|
+
/** What a connection's details say: the server, who reaches it, and the call running now. */
|
|
152
|
+
export declare function connectionDetails(connection: Pick<A2ATeamConnection, 'name' | 'tools'>, member: string | undefined, calling?: string): string;
|
|
85
153
|
/** The words on the edge for a flow. */
|
|
86
154
|
export declare function flowWords(flow: A2ATeamFlow, entry: string, peer: string): string;
|
|
87
155
|
type CallData = {
|
|
@@ -95,5 +163,5 @@ export declare function callWords(member: string, connection: A2ATeamConnection,
|
|
|
95
163
|
/** The viewport that shows the graph's width whole in a box `width` wide, never larger than drawn. */
|
|
96
164
|
export declare function teamViewport(width: number): Viewport;
|
|
97
165
|
/** Draw a team of two over A2A, the link moving with what travels on it. */
|
|
98
|
-
export declare function A2ATeamGraph({ entry, peer, flow, connected, label, size, calls, }: A2ATeamGraphProps): JSX.Element;
|
|
166
|
+
export declare function A2ATeamGraph({ entry, peer, flow, connected, label, size, calls, balloonRoom, }: A2ATeamGraphProps): JSX.Element;
|
|
99
167
|
export default A2ATeamGraph;
|