@datalayer/agent-runtimes 1.3.84 → 1.3.85
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 +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +14 -11
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +3 -0
- package/lib/components/teams/index.js +3 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/loop/apps/AppComposer.d.ts +2 -1
- package/lib/loop/apps/AppComposer.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +2 -1
- package/lib/loop/apps/AppRenderer.js +34 -25
- package/lib/loop/apps/appspec.d.ts +11 -1
- package/lib/loop/apps/appspec.js +115 -1
- package/lib/loop/apps/checks.d.ts +8 -0
- package/lib/loop/apps/checks.js +106 -4
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +1 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +11 -3
- package/lib/loop/embed/AppEmbed.js +42 -16
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +61 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/hostBridge.d.ts +28 -2
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.js +4 -1
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +120 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +6 -4
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1384 -231
- package/lib/specs/appspecSchema.js +191 -1
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/types/agentspecs.d.ts +70 -0
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +4 -2
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* What agents do in the page, as OpenTelemetry spans: the record the Agent
|
|
7
|
+
* Inspector reads. A turn is an `invoke_agent` span, each tool call an
|
|
8
|
+
* `execute_tool` span under it — the GenAI semantic conventions the runtime's
|
|
9
|
+
* own spans follow (pydantic-ai's): `gen_ai.operation.name`,
|
|
10
|
+
* `gen_ai.agent.name`, `gen_ai.request.model`, `gen_ai.tool.name`,
|
|
11
|
+
* `gen_ai.tool.call.id`, `gen_ai.tool.call.arguments` and `.result` (JSON
|
|
12
|
+
* text), `gen_ai.input.messages` and `gen_ai.output.messages`, the
|
|
13
|
+
* `gen_ai.usage.*` tokens. An agent is a service: `service.name` is its name.
|
|
14
|
+
*
|
|
15
|
+
* Where a tool comes from — an MCP server, the page (a frontend tool), a
|
|
16
|
+
* skill, codemode, the runtime — no convention says: `datalayer.tool.kind`
|
|
17
|
+
* does, beside `gen_ai.tool.type`, with the tool's marks
|
|
18
|
+
* (`datalayer.mark.icon`, `datalayer.mark.emoji`).
|
|
19
|
+
*
|
|
20
|
+
* The tracer is core's (`createOtelLiveTracer`): one a page, shared by its
|
|
21
|
+
* chats and its team (`AgentInspectorProvider`, or the `inspector` prop).
|
|
22
|
+
*
|
|
23
|
+
* @module components/inspector/agentSpans
|
|
24
|
+
*/
|
|
25
|
+
import { createContext, createElement, useContext, useState, } from 'react';
|
|
26
|
+
import { createOtelLiveTracer, } from '@datalayer/core/lib/otel/live';
|
|
27
|
+
/** The attributes the record uses: the GenAI conventions, and a few of Datalayer's. */
|
|
28
|
+
export const AGENT_SPAN_ATTRIBUTES = {
|
|
29
|
+
operation: 'gen_ai.operation.name',
|
|
30
|
+
agentName: 'gen_ai.agent.name',
|
|
31
|
+
conversationId: 'gen_ai.conversation.id',
|
|
32
|
+
requestModel: 'gen_ai.request.model',
|
|
33
|
+
inputMessages: 'gen_ai.input.messages',
|
|
34
|
+
outputMessages: 'gen_ai.output.messages',
|
|
35
|
+
inputTokens: 'gen_ai.usage.input_tokens',
|
|
36
|
+
outputTokens: 'gen_ai.usage.output_tokens',
|
|
37
|
+
toolName: 'gen_ai.tool.name',
|
|
38
|
+
toolCallId: 'gen_ai.tool.call.id',
|
|
39
|
+
toolArguments: 'gen_ai.tool.call.arguments',
|
|
40
|
+
toolResult: 'gen_ai.tool.call.result',
|
|
41
|
+
toolType: 'gen_ai.tool.type',
|
|
42
|
+
/** `mcp`, `frontend`, `skill`, `codemode`, `runtime`. */
|
|
43
|
+
toolKind: 'datalayer.tool.kind',
|
|
44
|
+
markIcon: 'datalayer.mark.icon',
|
|
45
|
+
markEmoji: 'datalayer.mark.emoji',
|
|
46
|
+
/** How the page knows of a span another process did: `a2a.status`. */
|
|
47
|
+
origin: 'datalayer.span.origin',
|
|
48
|
+
errorType: 'error.type',
|
|
49
|
+
};
|
|
50
|
+
const A = AGENT_SPAN_ATTRIBUTES;
|
|
51
|
+
/** Every kind, in the order the filters list them. */
|
|
52
|
+
export const AGENT_TOOL_KINDS = [
|
|
53
|
+
'mcp',
|
|
54
|
+
'frontend',
|
|
55
|
+
'skill',
|
|
56
|
+
'codemode',
|
|
57
|
+
'runtime',
|
|
58
|
+
];
|
|
59
|
+
/** A value as an attribute carries it: JSON text. */
|
|
60
|
+
export function jsonAttribute(value) {
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(value);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return String(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** A line short enough for a row. */
|
|
72
|
+
export function shortLine(text, length = 160) {
|
|
73
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
74
|
+
return flat.length > length ? `${flat.slice(0, length - 1)}…` : flat;
|
|
75
|
+
}
|
|
76
|
+
/** A call's arguments, in a few words: `kind=customer, limit=100`. */
|
|
77
|
+
export function argumentsLine(args, length = 100) {
|
|
78
|
+
if (!args || typeof args !== 'object') {
|
|
79
|
+
return args === undefined ? '' : shortLine(String(args), length);
|
|
80
|
+
}
|
|
81
|
+
const parts = Object.entries(args).map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`);
|
|
82
|
+
return shortLine(parts.join(', '), length);
|
|
83
|
+
}
|
|
84
|
+
/** A message in the GenAI conventions' shape: its role and its text part. */
|
|
85
|
+
function messages(role, text) {
|
|
86
|
+
return JSON.stringify([{ role, parts: [{ type: 'text', content: text }] }]);
|
|
87
|
+
}
|
|
88
|
+
/** A turn of an agent: from the question to the answer. */
|
|
89
|
+
export function startAgentTurn(tracer, turn) {
|
|
90
|
+
return tracer.startSpan(`invoke_agent ${turn.agent}`, {
|
|
91
|
+
serviceName: turn.agent,
|
|
92
|
+
parent: null,
|
|
93
|
+
attributes: {
|
|
94
|
+
[A.operation]: 'invoke_agent',
|
|
95
|
+
[A.agentName]: turn.agent,
|
|
96
|
+
[A.requestModel]: turn.model || undefined,
|
|
97
|
+
[A.conversationId]: turn.conversationId,
|
|
98
|
+
[A.inputMessages]: messages('user', turn.prompt),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** End a turn: its answer, its tokens, or its error. */
|
|
103
|
+
export function endAgentTurn(span, outcome) {
|
|
104
|
+
const usage = outcome.usage;
|
|
105
|
+
span.setAttributes({
|
|
106
|
+
[A.outputMessages]: outcome.text
|
|
107
|
+
? messages('assistant', outcome.text)
|
|
108
|
+
: undefined,
|
|
109
|
+
[A.inputTokens]: usage?.inputTokens ?? usage?.promptTokens,
|
|
110
|
+
[A.outputTokens]: usage?.outputTokens ?? usage?.completionTokens,
|
|
111
|
+
});
|
|
112
|
+
if (outcome.error) {
|
|
113
|
+
span.setAttribute(A.errorType, 'error');
|
|
114
|
+
span.recordException(new Error(outcome.error));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
span.setStatus('OK');
|
|
118
|
+
}
|
|
119
|
+
span.end();
|
|
120
|
+
}
|
|
121
|
+
/** `gen_ai.tool.type` for a kind: an MCP server's tool is an extension. */
|
|
122
|
+
function toolType(kind) {
|
|
123
|
+
return kind === 'mcp' ? 'extension' : 'function';
|
|
124
|
+
}
|
|
125
|
+
/** A tool call: its name, id, arguments, kind and marks. */
|
|
126
|
+
export function startToolCall(tracer, call) {
|
|
127
|
+
return tracer.startSpan(`execute_tool ${call.name}`, {
|
|
128
|
+
serviceName: call.agent,
|
|
129
|
+
...(call.parent ? { parent: call.parent } : {}),
|
|
130
|
+
attributes: {
|
|
131
|
+
[A.operation]: 'execute_tool',
|
|
132
|
+
[A.agentName]: call.agent,
|
|
133
|
+
[A.toolName]: call.name,
|
|
134
|
+
[A.toolCallId]: call.id,
|
|
135
|
+
[A.toolArguments]: jsonAttribute(call.args ?? {}),
|
|
136
|
+
[A.toolType]: toolType(call.tool.kind),
|
|
137
|
+
[A.toolKind]: call.tool.kind,
|
|
138
|
+
[A.markIcon]: call.tool.icon,
|
|
139
|
+
[A.markEmoji]: call.tool.emoji,
|
|
140
|
+
[A.origin]: call.origin,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** End a tool call: its result, or its error. */
|
|
145
|
+
export function endToolCall(span, outcome) {
|
|
146
|
+
span.setAttribute(A.toolResult, jsonAttribute(outcome.result));
|
|
147
|
+
if (outcome.error) {
|
|
148
|
+
span.setAttribute(A.errorType, 'tool_error');
|
|
149
|
+
span.recordException(new Error(outcome.error));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
span.setStatus('OK');
|
|
153
|
+
}
|
|
154
|
+
span.end();
|
|
155
|
+
}
|
|
156
|
+
/** Where a span comes from, for the Inspector's *Source* filter. */
|
|
157
|
+
export function sourceOfSpan(span) {
|
|
158
|
+
const attributes = span.attributes ?? {};
|
|
159
|
+
if (attributes[A.toolKind]) {
|
|
160
|
+
return String(attributes[A.toolKind]);
|
|
161
|
+
}
|
|
162
|
+
if (attributes['rpc.service'] === 'a2a' ||
|
|
163
|
+
span.span_name.startsWith('a2a ')) {
|
|
164
|
+
return 'a2a';
|
|
165
|
+
}
|
|
166
|
+
if (attributes[A.operation] === 'invoke_agent') {
|
|
167
|
+
return 'model';
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
/** A source, in words. */
|
|
172
|
+
export const SPAN_SOURCE_LABELS = {
|
|
173
|
+
a2a: 'A2A',
|
|
174
|
+
mcp: 'MCP',
|
|
175
|
+
frontend: 'Frontend tool',
|
|
176
|
+
skill: 'Skill',
|
|
177
|
+
codemode: 'Codemode',
|
|
178
|
+
runtime: 'Runtime tool',
|
|
179
|
+
model: 'Model',
|
|
180
|
+
};
|
|
181
|
+
/** The text of the first message of a GenAI messages attribute. */
|
|
182
|
+
function messageText(value) {
|
|
183
|
+
if (typeof value !== 'string') {
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(value);
|
|
188
|
+
return (parsed[0]?.parts ?? [])
|
|
189
|
+
.map(part => (typeof part.content === 'string' ? part.content : ''))
|
|
190
|
+
.join('');
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** One line that says what a span carried: a call's arguments, a turn's question and answer, a message's words. */
|
|
197
|
+
export function describeAgentSpan(span) {
|
|
198
|
+
const attributes = span.attributes ?? {};
|
|
199
|
+
const operation = attributes[A.operation];
|
|
200
|
+
if (operation === 'execute_tool') {
|
|
201
|
+
let args;
|
|
202
|
+
try {
|
|
203
|
+
args = JSON.parse(String(attributes[A.toolArguments] ?? '{}'));
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
args = attributes[A.toolArguments];
|
|
207
|
+
}
|
|
208
|
+
return argumentsLine(args) || undefined;
|
|
209
|
+
}
|
|
210
|
+
if (operation === 'invoke_agent') {
|
|
211
|
+
const asked = messageText(attributes[A.inputMessages]);
|
|
212
|
+
const said = messageText(attributes[A.outputMessages]);
|
|
213
|
+
const input = Number(attributes[A.inputTokens] ?? 0);
|
|
214
|
+
const output = Number(attributes[A.outputTokens] ?? 0);
|
|
215
|
+
const tokens = input + output ? ` · ${input + output} tokens` : '';
|
|
216
|
+
return (shortLine(said ? `${shortLine(asked, 60)} → ${said}` : asked) + tokens);
|
|
217
|
+
}
|
|
218
|
+
const text = attributes['a2a.message.text'];
|
|
219
|
+
const state = attributes['a2a.task.state'];
|
|
220
|
+
const words = [
|
|
221
|
+
typeof state === 'string' ? stateWord(state) : '',
|
|
222
|
+
typeof text === 'string' ? shortLine(text) : '',
|
|
223
|
+
].filter(Boolean);
|
|
224
|
+
if (words.length) {
|
|
225
|
+
return words.join(' · ');
|
|
226
|
+
}
|
|
227
|
+
const skills = attributes['a2a.agent.skills'];
|
|
228
|
+
return Array.isArray(skills) ? skills.join(', ') : undefined;
|
|
229
|
+
}
|
|
230
|
+
/** `TASK_STATE_WORKING` is `working`. */
|
|
231
|
+
export function stateWord(state) {
|
|
232
|
+
return typeof state === 'string'
|
|
233
|
+
? state
|
|
234
|
+
.replace(/^TASK_STATE_/, '')
|
|
235
|
+
.toLowerCase()
|
|
236
|
+
.replace(/_/g, ' ')
|
|
237
|
+
: '';
|
|
238
|
+
}
|
|
239
|
+
/** A tracer for a page, made once (or the one given). */
|
|
240
|
+
export function useAgentInspector(given) {
|
|
241
|
+
const [own] = useState(() => given ?? createOtelLiveTracer({ serviceName: 'Agent' }));
|
|
242
|
+
return { tracer: given ?? own };
|
|
243
|
+
}
|
|
244
|
+
/** The tracer the chats under it record into. */
|
|
245
|
+
export const AgentInspectorContext = createContext(null);
|
|
246
|
+
/** The tracer of the nearest {@link AgentInspectorProvider}, if any. */
|
|
247
|
+
export function useAgentInspectorTracer() {
|
|
248
|
+
return useContext(AgentInspectorContext);
|
|
249
|
+
}
|
|
250
|
+
/** Every chat under it records what its agent does into `tracer`. */
|
|
251
|
+
export function AgentInspectorProvider({ tracer, children, }) {
|
|
252
|
+
return createElement(AgentInspectorContext.Provider, { value: tracer }, children);
|
|
253
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A chat's agent, as OpenTelemetry spans: each turn an `invoke_agent` span
|
|
3
|
+
* (the prompt, the answer, the tokens), each tool call an `execute_tool` span
|
|
4
|
+
* under it from its start to its end — an MCP server's, a skill, a frontend
|
|
5
|
+
* tool the page runs, codemode's, a tool the runtime runs — whatever
|
|
6
|
+
* transport the chat speaks (AG-UI, Vercel AI, A2A, the in-browser harness):
|
|
7
|
+
* `ChatBase` tells it what its adapter told.
|
|
8
|
+
*
|
|
9
|
+
* @module components/inspector/chatSpans
|
|
10
|
+
*/
|
|
11
|
+
import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
|
|
12
|
+
import type { MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
13
|
+
import { type TurnUsage } from './agentSpans';
|
|
14
|
+
export type ChatSpanRecorder = {
|
|
15
|
+
turnStarted: (prompt: string, model?: string) => void;
|
|
16
|
+
turnEnded: (outcome: {
|
|
17
|
+
text?: string;
|
|
18
|
+
usage?: TurnUsage;
|
|
19
|
+
error?: string;
|
|
20
|
+
}) => void;
|
|
21
|
+
toolStarted: (call: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
args: Record<string, unknown>;
|
|
25
|
+
}) => void;
|
|
26
|
+
toolEnded: (id: string, outcome: {
|
|
27
|
+
result?: unknown;
|
|
28
|
+
error?: string;
|
|
29
|
+
}) => void;
|
|
30
|
+
};
|
|
31
|
+
/** A recorder for one chat's agent (`agent`) into `tracer`. */
|
|
32
|
+
export declare function chatSpanRecorder(tracer: OtelLiveTracer, agent: () => string, options?: {
|
|
33
|
+
/** The page's own tools, by name: frontend tools. */
|
|
34
|
+
frontendTools?: () => readonly string[];
|
|
35
|
+
mcpServers?: () => readonly MarkedMcpServer[];
|
|
36
|
+
}): ChatSpanRecorder;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { endAgentTurn, endToolCall, startAgentTurn, startToolCall, } from './agentSpans';
|
|
6
|
+
import { classifyToolCall } from './classify';
|
|
7
|
+
/** A recorder for one chat's agent (`agent`) into `tracer`. */
|
|
8
|
+
export function chatSpanRecorder(tracer, agent, options = {}) {
|
|
9
|
+
let turn;
|
|
10
|
+
const tools = new Map();
|
|
11
|
+
return {
|
|
12
|
+
turnStarted(prompt, model) {
|
|
13
|
+
turn = startAgentTurn(tracer, { agent: agent(), prompt, model });
|
|
14
|
+
},
|
|
15
|
+
turnEnded(outcome) {
|
|
16
|
+
if (!turn) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
endAgentTurn(turn, outcome);
|
|
20
|
+
turn = undefined;
|
|
21
|
+
},
|
|
22
|
+
toolStarted({ id, name, args }) {
|
|
23
|
+
const known = tools.get(id);
|
|
24
|
+
if (known) {
|
|
25
|
+
// Arguments that stream in: the latest whole.
|
|
26
|
+
known.setAttribute('gen_ai.tool.call.arguments', JSON.stringify(args ?? {}));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
tools.set(id, startToolCall(tracer, {
|
|
30
|
+
agent: agent(),
|
|
31
|
+
name,
|
|
32
|
+
id,
|
|
33
|
+
args,
|
|
34
|
+
tool: classifyToolCall(name, args, {
|
|
35
|
+
frontendTools: options.frontendTools?.(),
|
|
36
|
+
mcpServers: options.mcpServers?.(),
|
|
37
|
+
}),
|
|
38
|
+
...(turn ? { parent: turn.context } : {}),
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
toolEnded(id, outcome) {
|
|
42
|
+
const span = tools.get(id);
|
|
43
|
+
if (!span) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
tools.delete(id);
|
|
47
|
+
endToolCall(span, outcome);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Where a tool call comes from, for
|
|
3
|
-
* catalogue says of its name. Apart from the
|
|
4
|
-
*
|
|
2
|
+
* Where a tool call comes from, for its span (`datalayer.tool.kind`), by
|
|
3
|
+
* what the catalogue says of its name. Apart from the spans themselves,
|
|
4
|
+
* which a page can record without loading the catalogues.
|
|
5
5
|
*
|
|
6
6
|
* @module components/inspector/classify
|
|
7
7
|
*/
|
|
8
8
|
import { type MarkedMcpServer } from '../../chat/marks/toolMarks';
|
|
9
|
-
import type {
|
|
9
|
+
import type { AgentToolKind, ToolClass } from './agentSpans';
|
|
10
|
+
/** The tools codemode exposes in place of the MCP tools it reaches. */
|
|
11
|
+
export declare const CODEMODE_TOOLS: ReadonlySet<string>;
|
|
10
12
|
/**
|
|
11
|
-
* Where a tool call comes from, by who claims its name: a
|
|
12
|
-
* `mcpServers`, a frontend tool
|
|
13
|
-
* a runtime tool; otherwise `fallback` — a tool
|
|
13
|
+
* Where a tool call comes from, by who claims its name: a frontend tool
|
|
14
|
+
* (one of `frontendTools`), a skill, one of `mcpServers`, a frontend tool of
|
|
15
|
+
* the catalogue, a runtime tool, codemode's; otherwise `fallback` — a tool
|
|
16
|
+
* the runtime runs.
|
|
14
17
|
*/
|
|
15
18
|
export declare function classifyToolCall(name: string, args?: Record<string, unknown>, options?: {
|
|
16
19
|
mcpServers?: readonly MarkedMcpServer[];
|
|
17
20
|
frontendTools?: readonly string[];
|
|
18
|
-
fallback?:
|
|
21
|
+
fallback?: AgentToolKind;
|
|
19
22
|
}): ToolClass;
|
|
@@ -3,39 +3,38 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* Where a tool call comes from, for
|
|
7
|
-
* catalogue says of its name. Apart from the
|
|
8
|
-
*
|
|
6
|
+
* Where a tool call comes from, for its span (`datalayer.tool.kind`), by
|
|
7
|
+
* what the catalogue says of its name. Apart from the spans themselves,
|
|
8
|
+
* which a page can record without loading the catalogues.
|
|
9
9
|
*
|
|
10
10
|
* @module components/inspector/classify
|
|
11
11
|
*/
|
|
12
12
|
import { marksOfToolCall, } from '../../chat/marks/toolMarks';
|
|
13
|
+
/** The tools codemode exposes in place of the MCP tools it reaches. */
|
|
14
|
+
export const CODEMODE_TOOLS = new Set([
|
|
15
|
+
'list_servers',
|
|
16
|
+
'list_tool_names',
|
|
17
|
+
'search_tools',
|
|
18
|
+
'get_tool_details',
|
|
19
|
+
'execute_code',
|
|
20
|
+
]);
|
|
13
21
|
/**
|
|
14
|
-
* Where a tool call comes from, by who claims its name: a
|
|
15
|
-
* `mcpServers`, a frontend tool
|
|
16
|
-
* a runtime tool; otherwise `fallback` — a tool
|
|
22
|
+
* Where a tool call comes from, by who claims its name: a frontend tool
|
|
23
|
+
* (one of `frontendTools`), a skill, one of `mcpServers`, a frontend tool of
|
|
24
|
+
* the catalogue, a runtime tool, codemode's; otherwise `fallback` — a tool
|
|
25
|
+
* the runtime runs.
|
|
17
26
|
*/
|
|
18
27
|
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
28
|
const marks = marksOfToolCall(name, args, options.mcpServers);
|
|
28
|
-
const
|
|
29
|
-
? '
|
|
30
|
-
: marks?.kind
|
|
31
|
-
?
|
|
32
|
-
:
|
|
33
|
-
? '
|
|
34
|
-
:
|
|
35
|
-
? 'backend-tool'
|
|
36
|
-
: (options.fallback ?? 'backend-tool');
|
|
29
|
+
const kind = options.frontendTools?.includes(name)
|
|
30
|
+
? 'frontend'
|
|
31
|
+
: marks?.kind
|
|
32
|
+
? marks.kind
|
|
33
|
+
: CODEMODE_TOOLS.has(name)
|
|
34
|
+
? 'codemode'
|
|
35
|
+
: (options.fallback ?? 'runtime');
|
|
37
36
|
return {
|
|
38
|
-
|
|
37
|
+
kind,
|
|
39
38
|
...(marks?.icon ? { icon: marks.icon } : {}),
|
|
40
39
|
...(marks?.emoji ? { emoji: marks.emoji } : {}),
|
|
41
40
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Agent Inspector: what agents do —
|
|
3
|
-
* frontend
|
|
4
|
-
*
|
|
2
|
+
* The Agent Inspector: what agents do — model turns, tool calls (MCP,
|
|
3
|
+
* frontend, skills, codemode, runtime), A2A requests and what came back —
|
|
4
|
+
* recorded as OpenTelemetry spans in the page's tracer (core's
|
|
5
|
+
* `createOtelLiveTracer`) and drawn by core's OTEL view (`OtelLiveSpans`).
|
|
5
6
|
*
|
|
6
7
|
* @module components/inspector
|
|
7
8
|
*/
|
|
8
|
-
export * from './
|
|
9
|
+
export * from './agentSpans';
|
|
9
10
|
export * from './classify';
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './JsonTree';
|
|
11
|
+
export * from './chatSpans';
|
|
12
|
+
export * from './a2aSpans';
|
|
13
13
|
export * from './AgentInspector';
|
|
14
14
|
export * from './AgentInspectorDialog';
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* The Agent Inspector: what agents do —
|
|
7
|
-
* frontend
|
|
8
|
-
*
|
|
6
|
+
* The Agent Inspector: what agents do — model turns, tool calls (MCP,
|
|
7
|
+
* frontend, skills, codemode, runtime), A2A requests and what came back —
|
|
8
|
+
* recorded as OpenTelemetry spans in the page's tracer (core's
|
|
9
|
+
* `createOtelLiveTracer`) and drawn by core's OTEL view (`OtelLiveSpans`).
|
|
9
10
|
*
|
|
10
11
|
* @module components/inspector
|
|
11
12
|
*/
|
|
12
|
-
export * from './
|
|
13
|
+
export * from './agentSpans';
|
|
13
14
|
export * from './classify';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './
|
|
16
|
-
export * from './JsonTree';
|
|
15
|
+
export * from './chatSpans';
|
|
16
|
+
export * from './a2aSpans';
|
|
17
17
|
export * from './AgentInspector';
|
|
18
18
|
export * from './AgentInspectorDialog';
|
|
@@ -30,7 +30,7 @@ import type { BalloonSuggestion } from '../../chat/assistant/SpeechBalloon';
|
|
|
30
30
|
import type { DisplayItem } from '../../types/chat';
|
|
31
31
|
import type { AssistantMenuItem } from '../../chat/assistant/AssistantContextMenu';
|
|
32
32
|
import type { AssistantSandbox } from '../../chat/assistant/assistantDetails';
|
|
33
|
-
import type {
|
|
33
|
+
import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
|
|
34
34
|
import { type A2ATeamCall, type A2ATeamConnection, type A2ATeamFlow } from './a2aTeamFlow';
|
|
35
35
|
import type { A2ATeamPersona } from './useA2ATeam';
|
|
36
36
|
import type { BalloonExpandTarget } from '../../chat/assistant/BalloonVisual';
|
|
@@ -78,10 +78,10 @@ export type A2ATeamGraphMember = {
|
|
|
78
78
|
suggestions?: readonly BalloonSuggestion[];
|
|
79
79
|
onSuggestion?: (suggestion: BalloonSuggestion) => void;
|
|
80
80
|
/**
|
|
81
|
-
* The team's Agent Inspector: its menu offers *Inspect the
|
|
82
|
-
* own
|
|
81
|
+
* The team's Agent Inspector's tracer: its menu offers *Inspect the
|
|
82
|
+
* agent…*, its own spans (what it did, and the A2A requests sent to it).
|
|
83
83
|
*/
|
|
84
|
-
inspector?:
|
|
84
|
+
inspector?: OtelLiveTracer | null;
|
|
85
85
|
/** Stops its turn, from its menu, while it works. */
|
|
86
86
|
onStop?: () => void;
|
|
87
87
|
/** What its menu's *About* and *Agent Details…* say. */
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transcript of a scene, drawn (LOOP A-06): the lines
|
|
3
|
+
* {@link transcriptOfSpans} or {@link transcriptOfRecord} built, each with
|
|
4
|
+
* its time, who speaks or acts and to whom, and the words or the tool —
|
|
5
|
+
* and *Copy as text*, which copies them with their times
|
|
6
|
+
* ({@link transcriptText}).
|
|
7
|
+
*
|
|
8
|
+
* @module components/teams/SceneTranscript
|
|
9
|
+
*/
|
|
10
|
+
import type { JSX } from 'react';
|
|
11
|
+
import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
|
|
12
|
+
import { type SceneTranscriptLine, type SceneTranscriptMember } from './sceneTranscript';
|
|
13
|
+
/** The transcript of a tracer's spans, kept current as they change. */
|
|
14
|
+
export declare function useSceneTranscript(tracer: OtelLiveTracer | null | undefined, members: readonly SceneTranscriptMember[]): SceneTranscriptLine[];
|
|
15
|
+
export type SceneTranscriptProps = {
|
|
16
|
+
/** The lines, in order. */
|
|
17
|
+
lines: readonly SceneTranscriptLine[];
|
|
18
|
+
/** What is said while there is no line yet. */
|
|
19
|
+
emptyText?: string;
|
|
20
|
+
/** How tall it grows before it scrolls, in pixels. */
|
|
21
|
+
maxHeight?: number;
|
|
22
|
+
/** Whether *Copy as text* is offered (it is, when there is a line). */
|
|
23
|
+
copy?: boolean;
|
|
24
|
+
/** The clipboard: `navigator.clipboard.writeText` unless a test gives one. */
|
|
25
|
+
writeText?: (text: string) => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
/** The transcript as a list of lines, newest last, with *Copy as text*. */
|
|
28
|
+
export declare function SceneTranscript({ lines, emptyText, maxHeight, copy, writeText, }: SceneTranscriptProps): JSX.Element;
|
|
29
|
+
export default SceneTranscript;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Button, Text } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { useOtelLiveSpans } from '@datalayer/core/lib/otel/live';
|
|
6
|
+
import { lineHeading, timeText, transcriptOfSpans, transcriptText, } from './sceneTranscript';
|
|
7
|
+
/** The transcript of a tracer's spans, kept current as they change. */
|
|
8
|
+
export function useSceneTranscript(tracer, members) {
|
|
9
|
+
const spans = useOtelLiveSpans(tracer);
|
|
10
|
+
return useMemo(() => transcriptOfSpans(spans, members), [spans, members]);
|
|
11
|
+
}
|
|
12
|
+
/** How long *Copied* stays, in milliseconds. */
|
|
13
|
+
const COPIED_MS = 1500;
|
|
14
|
+
/** The transcript as a list of lines, newest last, with *Copy as text*. */
|
|
15
|
+
export function SceneTranscript({ lines, emptyText = 'What the scene says and does shows here, line by line.', maxHeight, copy = true, writeText, }) {
|
|
16
|
+
const [copied, setCopied] = useState(false);
|
|
17
|
+
const onCopy = async () => {
|
|
18
|
+
const write = writeText ??
|
|
19
|
+
(text => globalThis.navigator?.clipboard?.writeText(text) ?? Promise.resolve());
|
|
20
|
+
try {
|
|
21
|
+
await write(transcriptText(lines));
|
|
22
|
+
setCopied(true);
|
|
23
|
+
setTimeout(() => setCopied(false), COPIED_MS);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// The clipboard refused: nothing to say but the text, still on screen.
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return (_jsxs(Box, { "data-scene-transcript": "", "data-scene-lines": lines.length, children: [lines.length === 0 ? (_jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: emptyText })) : (_jsx(Box, { as: "ol", "aria-label": "Transcript", sx: {
|
|
30
|
+
listStyle: 'none',
|
|
31
|
+
m: 0,
|
|
32
|
+
p: 0,
|
|
33
|
+
fontSize: 1,
|
|
34
|
+
...(maxHeight ? { maxHeight, overflowY: 'auto' } : {}),
|
|
35
|
+
}, children: lines.map(line => (_jsxs(Box, { as: "li", "data-transcript-line": line.kind, "data-transcript-from": line.from, "data-transcript-to": line.to, "aria-busy": line.open ? 'true' : undefined, sx: {
|
|
36
|
+
display: 'grid',
|
|
37
|
+
gridTemplateColumns: 'auto 1fr',
|
|
38
|
+
gap: 2,
|
|
39
|
+
py: 1,
|
|
40
|
+
borderBottom: '1px solid',
|
|
41
|
+
borderColor: 'border.muted',
|
|
42
|
+
opacity: line.open ? 0.75 : 1,
|
|
43
|
+
}, children: [_jsx(Text, { as: "time", sx: {
|
|
44
|
+
color: 'fg.muted',
|
|
45
|
+
fontFamily: 'mono',
|
|
46
|
+
fontSize: 0,
|
|
47
|
+
pt: '2px',
|
|
48
|
+
whiteSpace: 'nowrap',
|
|
49
|
+
}, dateTime: new Date(line.at).toISOString(), children: timeText(line.at) }), _jsxs(Box, { sx: { minWidth: 0, overflowWrap: 'anywhere' }, children: [_jsxs(Text, { sx: { fontWeight: 600 }, children: [lineHeading(line), ": "] }), _jsxs(Text, { sx: {
|
|
50
|
+
fontFamily: line.kind === 'called' ? 'mono' : undefined,
|
|
51
|
+
color: line.failed ? 'danger.fg' : undefined,
|
|
52
|
+
}, children: [line.text, line.open ? '…' : '', line.failed ? ' (failed)' : ''] })] })] }, line.key))) })), copy && lines.length > 0 && (_jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { size: "small", onClick: () => void onCopy(), "data-transcript-copy": "", children: copied ? 'Copied' : 'Copy as text' }) }))] }));
|
|
53
|
+
}
|
|
54
|
+
export default SceneTranscript;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scene on a page: its graph (`A2ATeamGraph`) and its transcript
|
|
3
|
+
* ({@link SceneTranscript}), one shown at a time behind a *Graph · Transcript*
|
|
4
|
+
* toggle (LOOP A-06). The graph stays mounted while the transcript shows, so
|
|
5
|
+
* that what its members were doing is still there when it comes back.
|
|
6
|
+
*
|
|
7
|
+
* The lines come from the Agent Inspector's tracer the graph's members
|
|
8
|
+
* record into (`tracer`, with the `members` to name them), or are given
|
|
9
|
+
* (`lines`: a finished run's, from its record).
|
|
10
|
+
*
|
|
11
|
+
* @module components/teams/SceneView
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX, ReactNode } from 'react';
|
|
14
|
+
import type { OtelLiveTracer } from '@datalayer/core/lib/otel/live';
|
|
15
|
+
import type { SceneTranscriptLine, SceneTranscriptMember } from './sceneTranscript';
|
|
16
|
+
/** Which of the two the scene shows. */
|
|
17
|
+
export type SceneViewChoice = 'graph' | 'transcript';
|
|
18
|
+
export type SceneViewProps = {
|
|
19
|
+
/** The graph: an `A2ATeamGraph`, or whatever draws the scene. */
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/** The members, to name the spans' services and their connections. */
|
|
22
|
+
members?: readonly SceneTranscriptMember[];
|
|
23
|
+
/** The tracer the members record into: the transcript is read from it. */
|
|
24
|
+
tracer?: OtelLiveTracer | null;
|
|
25
|
+
/** The lines, given: a finished run's. Wins over `tracer`. */
|
|
26
|
+
lines?: readonly SceneTranscriptLine[];
|
|
27
|
+
/** What shows first: the graph unless said. */
|
|
28
|
+
defaultView?: SceneViewChoice;
|
|
29
|
+
/** The choice, held by the page; with `onViewChange`. */
|
|
30
|
+
view?: SceneViewChoice;
|
|
31
|
+
onViewChange?: (view: SceneViewChoice) => void;
|
|
32
|
+
/** What the transcript says while there is no line yet. */
|
|
33
|
+
emptyText?: string;
|
|
34
|
+
/** How tall the transcript grows before it scrolls, in pixels. */
|
|
35
|
+
maxHeight?: number;
|
|
36
|
+
/** Something of the page's beside the toggle: a clock, a line. */
|
|
37
|
+
aside?: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
/** The graph and the transcript of a scene, behind a *Graph · Transcript* toggle. */
|
|
40
|
+
export declare function SceneView({ children, members, tracer, lines, defaultView, view, onViewChange, emptyText, maxHeight, aside, }: SceneViewProps): JSX.Element;
|
|
41
|
+
export default SceneView;
|