@datalayer/agent-runtimes 1.3.84 → 1.3.86
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 +117 -22
- package/lib/components/teams/A2ATeamGraph.js +217 -97
- 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 +4 -0
- package/lib/components/teams/index.js +4 -0
- package/lib/components/teams/sceneStage.d.ts +120 -0
- package/lib/components/teams/sceneStage.js +170 -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 +94 -22
- package/lib/components/teams/useA2ATeam.js +356 -140
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/examples/ScenesExample.d.ts +26 -0
- package/lib/examples/ScenesExample.js +241 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/exampleGroups.js +3 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/examples/main.js +4 -0
- 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/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/mcpServers.js +8 -2
- package/lib/specs/scenes.d.ts +21 -0
- package/lib/specs/scenes.js +1191 -0
- 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/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/scenes.d.ts +207 -0
- package/lib/types/scenes.js +5 -0
- package/lib/types/teams.d.ts +13 -2
- package/package.json +4 -4
- package/scripts/codegen/generate_agents.py +1 -0
- package/scripts/codegen/generate_apps.py +4 -2
- package/scripts/codegen/generate_scenes.py +301 -0
- 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,373 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { endToolCall, jsonAttribute, shortLine, startToolCall, } from './agentSpans';
|
|
6
|
+
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
/** The text of an A2A 1.0 message's or artifact's parts, as JSON carries them. */
|
|
8
|
+
export function partsText(parts) {
|
|
9
|
+
return Array.isArray(parts)
|
|
10
|
+
? parts
|
|
11
|
+
.map(part => isObject(part) && typeof part.text === 'string' ? part.text : '')
|
|
12
|
+
.join('')
|
|
13
|
+
: '';
|
|
14
|
+
}
|
|
15
|
+
/** A JSON-RPC request's body, if the request carries one. */
|
|
16
|
+
function rpcOf(body) {
|
|
17
|
+
if (typeof body !== 'string') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(body);
|
|
22
|
+
return isObject(parsed) && parsed.jsonrpc === '2.0' ? parsed : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** The step a status message carries: a tool call or its end. */
|
|
29
|
+
function stepOf(message) {
|
|
30
|
+
if (!isObject(message) || !Array.isArray(message.parts)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
for (const part of message.parts) {
|
|
34
|
+
const data = isObject(part) ? part.data : undefined;
|
|
35
|
+
if (!isObject(data)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const call = isObject(data.tool_call) ? data.tool_call : undefined;
|
|
39
|
+
const done = isObject(data.tool_result) ? data.tool_result : undefined;
|
|
40
|
+
const told = call ?? done;
|
|
41
|
+
if (told && typeof told.name === 'string') {
|
|
42
|
+
return {
|
|
43
|
+
...(typeof told.id === 'string' ? { toolId: told.id } : {}),
|
|
44
|
+
name: told.name,
|
|
45
|
+
ended: !call,
|
|
46
|
+
...(call && isObject(call.arguments)
|
|
47
|
+
? { args: call.arguments }
|
|
48
|
+
: {}),
|
|
49
|
+
...(done && 'result' in done ? { result: done.result } : {}),
|
|
50
|
+
...(done && done.error ? { error: String(done.error) } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const FAILED_STATES = /FAILED|REJECTED|CANCELED/;
|
|
57
|
+
/** Records what came back for one request onto its span. */
|
|
58
|
+
class ResponseRecorder {
|
|
59
|
+
options;
|
|
60
|
+
span;
|
|
61
|
+
/** The chunks of each artifact being streamed, by its id. */
|
|
62
|
+
artifacts = new Map();
|
|
63
|
+
/** The peer's tool calls still open, by task and call. */
|
|
64
|
+
calls = new Map();
|
|
65
|
+
constructor(options, span) {
|
|
66
|
+
this.options = options;
|
|
67
|
+
this.span = span;
|
|
68
|
+
}
|
|
69
|
+
/** One JSON-RPC message of the response: a response, or an event of a stream. */
|
|
70
|
+
record(message, streamed) {
|
|
71
|
+
if (!isObject(message)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const whole = { 'a2a.event': jsonAttribute(message) };
|
|
75
|
+
if (isObject(message.error)) {
|
|
76
|
+
const error = message.error;
|
|
77
|
+
const words = `${String(error.code ?? '')} ${String(error.message ?? '')}`.trim();
|
|
78
|
+
this.span.addEvent('a2a.error', { ...whole, 'a2a.error': words });
|
|
79
|
+
this.span.setStatus('ERROR', words);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const result = isObject(message.result) ? message.result : undefined;
|
|
83
|
+
if (!result) {
|
|
84
|
+
this.span.addEvent('a2a.response', whole);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (isObject(result.task)) {
|
|
88
|
+
const task = result.task;
|
|
89
|
+
const status = isObject(task.status) ? task.status : {};
|
|
90
|
+
this.task(String(task.id ?? ''), String(task.contextId ?? ''), String(status.state ?? ''));
|
|
91
|
+
this.span.addEvent('a2a.task', {
|
|
92
|
+
...whole,
|
|
93
|
+
'a2a.task.id': String(task.id ?? ''),
|
|
94
|
+
'a2a.context.id': String(task.contextId ?? ''),
|
|
95
|
+
'a2a.task.state': String(status.state ?? ''),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else if (isObject(result.statusUpdate)) {
|
|
99
|
+
this.status(result.statusUpdate, whole);
|
|
100
|
+
}
|
|
101
|
+
else if (isObject(result.artifactUpdate)) {
|
|
102
|
+
this.artifact(result.artifactUpdate, message);
|
|
103
|
+
}
|
|
104
|
+
else if (isObject(result.message)) {
|
|
105
|
+
const reply = result.message;
|
|
106
|
+
const text = partsText(reply.parts);
|
|
107
|
+
this.span.addEvent('a2a.message', {
|
|
108
|
+
...whole,
|
|
109
|
+
'a2a.task.id': String(reply.taskId ?? ''),
|
|
110
|
+
'a2a.context.id': String(reply.contextId ?? ''),
|
|
111
|
+
'a2a.message.text': text,
|
|
112
|
+
});
|
|
113
|
+
this.span.setAttribute('a2a.answer.text', text || undefined);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.span.addEvent(streamed ? 'a2a.event' : 'a2a.response', whole);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** The task the span is about, and its latest state. */
|
|
120
|
+
task(taskId, contextId, state) {
|
|
121
|
+
this.span.setAttributes({
|
|
122
|
+
'a2a.task.id': taskId || undefined,
|
|
123
|
+
'a2a.context.id': contextId || undefined,
|
|
124
|
+
'a2a.task.state': state || undefined,
|
|
125
|
+
});
|
|
126
|
+
if (FAILED_STATES.test(state)) {
|
|
127
|
+
this.span.setStatus('ERROR', state);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
status(update, whole) {
|
|
131
|
+
const status = isObject(update.status) ? update.status : {};
|
|
132
|
+
const taskId = String(update.taskId ?? '');
|
|
133
|
+
const state = String(status.state ?? '');
|
|
134
|
+
const step = stepOf(status.message);
|
|
135
|
+
const text = partsText(isObject(status.message) ? status.message.parts : undefined);
|
|
136
|
+
this.task(taskId, String(update.contextId ?? ''), state);
|
|
137
|
+
this.span.addEvent('a2a.status_update', {
|
|
138
|
+
...whole,
|
|
139
|
+
'a2a.task.id': taskId,
|
|
140
|
+
'a2a.context.id': String(update.contextId ?? ''),
|
|
141
|
+
'a2a.task.state': state,
|
|
142
|
+
'a2a.message.text': text || undefined,
|
|
143
|
+
'gen_ai.tool.name': step?.name,
|
|
144
|
+
'gen_ai.tool.call.id': step?.toolId,
|
|
145
|
+
});
|
|
146
|
+
if (!step) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// The call, as a span of the peer's: from its start to its end.
|
|
150
|
+
const key = `${taskId}:${step.toolId ?? step.name}`;
|
|
151
|
+
if (!step.ended) {
|
|
152
|
+
if (this.calls.has(key)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const { peer, classifyTool } = this.options;
|
|
156
|
+
this.calls.set(key, startToolCall(this.options.tracer, {
|
|
157
|
+
agent: peer,
|
|
158
|
+
name: step.name,
|
|
159
|
+
id: step.toolId,
|
|
160
|
+
args: step.args ?? {},
|
|
161
|
+
tool: classifyTool?.(step.name, step.args) ?? { kind: 'runtime' },
|
|
162
|
+
parent: this.span.context,
|
|
163
|
+
origin: 'a2a.status',
|
|
164
|
+
}));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const call = this.calls.get(key);
|
|
168
|
+
if (call) {
|
|
169
|
+
this.calls.delete(key);
|
|
170
|
+
endToolCall(call, { result: step.result, error: step.error });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
artifact(update, message) {
|
|
174
|
+
const artifact = isObject(update.artifact) ? update.artifact : {};
|
|
175
|
+
const artifactId = String(artifact.artifactId ?? '');
|
|
176
|
+
const text = partsText(artifact.parts);
|
|
177
|
+
const parts = Array.isArray(artifact.parts) ? artifact.parts : [];
|
|
178
|
+
const media = parts
|
|
179
|
+
.map(part => (isObject(part) ? part.mediaType : undefined))
|
|
180
|
+
.find(type => typeof type === 'string' && type && !type.startsWith('text/'));
|
|
181
|
+
const known = this.artifacts.get(artifactId);
|
|
182
|
+
const chunk = known ?? {
|
|
183
|
+
name: String(artifact.name ?? (artifactId || 'artifact')),
|
|
184
|
+
text: '',
|
|
185
|
+
events: [],
|
|
186
|
+
attributes: {
|
|
187
|
+
'a2a.task.id': String(update.taskId ?? ''),
|
|
188
|
+
'a2a.context.id': String(update.contextId ?? ''),
|
|
189
|
+
'a2a.artifact.id': artifactId,
|
|
190
|
+
'a2a.artifact.name': String(artifact.name ?? (artifactId || 'artifact')),
|
|
191
|
+
'a2a.artifact.media_type': typeof media === 'string' ? media : undefined,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
chunk.text += text;
|
|
195
|
+
chunk.events.push(message);
|
|
196
|
+
if (update.append && !update.lastChunk) {
|
|
197
|
+
// A chunk of an artifact being streamed: one event once it is whole.
|
|
198
|
+
this.artifacts.set(artifactId, chunk);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.artifacts.delete(artifactId);
|
|
202
|
+
this.emitArtifact(chunk);
|
|
203
|
+
}
|
|
204
|
+
emitArtifact(chunk) {
|
|
205
|
+
this.span.addEvent('a2a.artifact_update', {
|
|
206
|
+
...chunk.attributes,
|
|
207
|
+
'a2a.artifact.chunks': chunk.events.length,
|
|
208
|
+
'a2a.message.text': chunk.text ? shortLine(chunk.text, 2000) : undefined,
|
|
209
|
+
'a2a.event': jsonAttribute(chunk.events.length === 1 ? chunk.events[0] : chunk.events),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/** The response is over: what is still open is closed with it. */
|
|
213
|
+
finish(error) {
|
|
214
|
+
for (const chunk of this.artifacts.values()) {
|
|
215
|
+
this.emitArtifact(chunk);
|
|
216
|
+
}
|
|
217
|
+
this.artifacts.clear();
|
|
218
|
+
for (const call of this.calls.values()) {
|
|
219
|
+
call.end();
|
|
220
|
+
}
|
|
221
|
+
this.calls.clear();
|
|
222
|
+
if (error !== undefined) {
|
|
223
|
+
this.span.recordException(error);
|
|
224
|
+
}
|
|
225
|
+
this.span.end();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** The JSON of each event of a server-sent stream, as it arrives. */
|
|
229
|
+
async function readEvents(body, onEvent) {
|
|
230
|
+
const reader = body.getReader();
|
|
231
|
+
const decoder = new TextDecoder();
|
|
232
|
+
let buffer = '';
|
|
233
|
+
const flush = (block) => {
|
|
234
|
+
const data = block
|
|
235
|
+
.split(/\r?\n/)
|
|
236
|
+
.filter(line => line.startsWith('data:'))
|
|
237
|
+
.map(line => line.slice(5).replace(/^ /, ''))
|
|
238
|
+
.join('\n');
|
|
239
|
+
if (!data) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
onEvent(JSON.parse(data));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
onEvent({ unparsed: data });
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
for (;;) {
|
|
250
|
+
const { done, value } = await reader.read();
|
|
251
|
+
if (done) {
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
buffer += decoder.decode(value, { stream: true });
|
|
255
|
+
let at;
|
|
256
|
+
while ((at = buffer.search(/\r?\n\r?\n/)) >= 0) {
|
|
257
|
+
const block = buffer.slice(0, at);
|
|
258
|
+
buffer = buffer.slice(at).replace(/^\r?\n\r?\n/, '');
|
|
259
|
+
flush(block);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (buffer.trim()) {
|
|
263
|
+
flush(buffer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* A fetch that records the A2A traffic it carries as spans into
|
|
268
|
+
* `options.tracer`, and hands the SDK what the network gave.
|
|
269
|
+
*/
|
|
270
|
+
export function traceA2AFetch(base, options) {
|
|
271
|
+
const { tracer, asker, peer } = options;
|
|
272
|
+
return async (input, init) => {
|
|
273
|
+
const rpc = rpcOf(init?.body);
|
|
274
|
+
const method = rpc ? String(rpc.method ?? '') : '';
|
|
275
|
+
const url = typeof input === 'string'
|
|
276
|
+
? input
|
|
277
|
+
: input instanceof URL
|
|
278
|
+
? input.href
|
|
279
|
+
: input.url;
|
|
280
|
+
const httpMethod = (init?.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase();
|
|
281
|
+
let path = url;
|
|
282
|
+
try {
|
|
283
|
+
path = new URL(url, globalThis.location?.href).pathname;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// A URL the page cannot read: said whole.
|
|
287
|
+
}
|
|
288
|
+
const params = rpc && isObject(rpc.params) ? rpc.params : undefined;
|
|
289
|
+
const message = params && isObject(params.message) ? params.message : undefined;
|
|
290
|
+
const span = tracer.startSpan(rpc ? `a2a ${method}` : `${httpMethod} ${path}`, {
|
|
291
|
+
serviceName: asker,
|
|
292
|
+
kind: 'CLIENT',
|
|
293
|
+
attributes: {
|
|
294
|
+
'peer.service': peer,
|
|
295
|
+
'url.full': url,
|
|
296
|
+
'http.request.method': httpMethod,
|
|
297
|
+
...(rpc
|
|
298
|
+
? {
|
|
299
|
+
'rpc.system': 'jsonrpc',
|
|
300
|
+
'rpc.service': 'a2a',
|
|
301
|
+
'rpc.method': method,
|
|
302
|
+
'rpc.jsonrpc.version': '2.0',
|
|
303
|
+
'rpc.jsonrpc.request_id': rpc.id === undefined ? undefined : String(rpc.id),
|
|
304
|
+
'a2a.request': jsonAttribute(rpc),
|
|
305
|
+
'a2a.message.text': message
|
|
306
|
+
? partsText(message.parts) || undefined
|
|
307
|
+
: undefined,
|
|
308
|
+
'a2a.task.id': typeof params?.id === 'string' ? params.id : undefined,
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
const recorder = new ResponseRecorder(options, span);
|
|
314
|
+
let response;
|
|
315
|
+
try {
|
|
316
|
+
response = await base(input, init);
|
|
317
|
+
}
|
|
318
|
+
catch (reason) {
|
|
319
|
+
recorder.finish(reason);
|
|
320
|
+
throw reason;
|
|
321
|
+
}
|
|
322
|
+
span.setAttribute('http.response.status_code', response.status);
|
|
323
|
+
const type = response.headers.get('content-type') ?? '';
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
void response
|
|
326
|
+
.clone()
|
|
327
|
+
.text()
|
|
328
|
+
.then(text => {
|
|
329
|
+
span.setAttribute('a2a.error', shortLine(text, 2000) || undefined);
|
|
330
|
+
span.setAttribute('error.type', String(response.status));
|
|
331
|
+
recorder.finish(new Error(`${response.status} ${response.statusText}`.trim()));
|
|
332
|
+
})
|
|
333
|
+
.catch(reason => recorder.finish(reason));
|
|
334
|
+
return response;
|
|
335
|
+
}
|
|
336
|
+
if (type.includes('text/event-stream') && response.body) {
|
|
337
|
+
const [mine, theirs] = response.body.tee();
|
|
338
|
+
void readEvents(mine, data => recorder.record(data, true))
|
|
339
|
+
.then(() => recorder.finish())
|
|
340
|
+
.catch(reason => recorder.finish(reason));
|
|
341
|
+
return new Response(theirs, {
|
|
342
|
+
status: response.status,
|
|
343
|
+
statusText: response.statusText,
|
|
344
|
+
headers: response.headers,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
if (type.includes('json')) {
|
|
348
|
+
void response
|
|
349
|
+
.clone()
|
|
350
|
+
.json()
|
|
351
|
+
.then(data => {
|
|
352
|
+
if (rpc) {
|
|
353
|
+
recorder.record(data, false);
|
|
354
|
+
}
|
|
355
|
+
else if (isObject(data) && Array.isArray(data.skills)) {
|
|
356
|
+
// The agent card.
|
|
357
|
+
span.updateName('a2a agent card');
|
|
358
|
+
span.setAttributes({
|
|
359
|
+
'rpc.service': 'a2a',
|
|
360
|
+
'a2a.agent.name': String(data.name ?? peer),
|
|
361
|
+
'a2a.agent.skills': data.skills.map(skill => isObject(skill) ? String(skill.name ?? skill.id) : ''),
|
|
362
|
+
'a2a.agent_card': jsonAttribute(data),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
recorder.finish();
|
|
366
|
+
})
|
|
367
|
+
.catch(reason => recorder.finish(reason));
|
|
368
|
+
return response;
|
|
369
|
+
}
|
|
370
|
+
recorder.finish();
|
|
371
|
+
return response;
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What agents do in the page, as OpenTelemetry spans: the record the Agent
|
|
3
|
+
* Inspector reads. A turn is an `invoke_agent` span, each tool call an
|
|
4
|
+
* `execute_tool` span under it — the GenAI semantic conventions the runtime's
|
|
5
|
+
* own spans follow (pydantic-ai's): `gen_ai.operation.name`,
|
|
6
|
+
* `gen_ai.agent.name`, `gen_ai.request.model`, `gen_ai.tool.name`,
|
|
7
|
+
* `gen_ai.tool.call.id`, `gen_ai.tool.call.arguments` and `.result` (JSON
|
|
8
|
+
* text), `gen_ai.input.messages` and `gen_ai.output.messages`, the
|
|
9
|
+
* `gen_ai.usage.*` tokens. An agent is a service: `service.name` is its name.
|
|
10
|
+
*
|
|
11
|
+
* Where a tool comes from — an MCP server, the page (a frontend tool), a
|
|
12
|
+
* skill, codemode, the runtime — no convention says: `datalayer.tool.kind`
|
|
13
|
+
* does, beside `gen_ai.tool.type`, with the tool's marks
|
|
14
|
+
* (`datalayer.mark.icon`, `datalayer.mark.emoji`).
|
|
15
|
+
*
|
|
16
|
+
* The tracer is core's (`createOtelLiveTracer`): one a page, shared by its
|
|
17
|
+
* chats and its team (`AgentInspectorProvider`, or the `inspector` prop).
|
|
18
|
+
*
|
|
19
|
+
* @module components/inspector/agentSpans
|
|
20
|
+
*/
|
|
21
|
+
import { type JSX, type ReactNode } from 'react';
|
|
22
|
+
import { type OtelLiveSpan, type OtelLiveTracer, type OtelSpanContext } from '@datalayer/core/lib/otel/live';
|
|
23
|
+
import type { OtelSpan } from '@datalayer/core/lib/otel/types';
|
|
24
|
+
/** The attributes the record uses: the GenAI conventions, and a few of Datalayer's. */
|
|
25
|
+
export declare const AGENT_SPAN_ATTRIBUTES: {
|
|
26
|
+
readonly operation: "gen_ai.operation.name";
|
|
27
|
+
readonly agentName: "gen_ai.agent.name";
|
|
28
|
+
readonly conversationId: "gen_ai.conversation.id";
|
|
29
|
+
readonly requestModel: "gen_ai.request.model";
|
|
30
|
+
readonly inputMessages: "gen_ai.input.messages";
|
|
31
|
+
readonly outputMessages: "gen_ai.output.messages";
|
|
32
|
+
readonly inputTokens: "gen_ai.usage.input_tokens";
|
|
33
|
+
readonly outputTokens: "gen_ai.usage.output_tokens";
|
|
34
|
+
readonly toolName: "gen_ai.tool.name";
|
|
35
|
+
readonly toolCallId: "gen_ai.tool.call.id";
|
|
36
|
+
readonly toolArguments: "gen_ai.tool.call.arguments";
|
|
37
|
+
readonly toolResult: "gen_ai.tool.call.result";
|
|
38
|
+
readonly toolType: "gen_ai.tool.type";
|
|
39
|
+
/** `mcp`, `frontend`, `skill`, `codemode`, `runtime`. */
|
|
40
|
+
readonly toolKind: "datalayer.tool.kind";
|
|
41
|
+
readonly markIcon: "datalayer.mark.icon";
|
|
42
|
+
readonly markEmoji: "datalayer.mark.emoji";
|
|
43
|
+
/** How the page knows of a span another process did: `a2a.status`. */
|
|
44
|
+
readonly origin: "datalayer.span.origin";
|
|
45
|
+
readonly errorType: "error.type";
|
|
46
|
+
};
|
|
47
|
+
/** Where a tool call comes from. */
|
|
48
|
+
export type AgentToolKind = 'mcp' | 'frontend' | 'skill' | 'codemode' | 'runtime';
|
|
49
|
+
/** Every kind, in the order the filters list them. */
|
|
50
|
+
export declare const AGENT_TOOL_KINDS: readonly AgentToolKind[];
|
|
51
|
+
/** What a tool call is: where it comes from, and its marks. */
|
|
52
|
+
export type ToolClass = {
|
|
53
|
+
kind: AgentToolKind;
|
|
54
|
+
icon?: string;
|
|
55
|
+
emoji?: string;
|
|
56
|
+
};
|
|
57
|
+
/** A value as an attribute carries it: JSON text. */
|
|
58
|
+
export declare function jsonAttribute(value: unknown): string | undefined;
|
|
59
|
+
/** A line short enough for a row. */
|
|
60
|
+
export declare function shortLine(text: string, length?: number): string;
|
|
61
|
+
/** A call's arguments, in a few words: `kind=customer, limit=100`. */
|
|
62
|
+
export declare function argumentsLine(args: unknown, length?: number): string;
|
|
63
|
+
/** A turn of an agent: from the question to the answer. */
|
|
64
|
+
export declare function startAgentTurn(tracer: OtelLiveTracer, turn: {
|
|
65
|
+
agent: string;
|
|
66
|
+
prompt: string;
|
|
67
|
+
model?: string;
|
|
68
|
+
conversationId?: string;
|
|
69
|
+
}): OtelLiveSpan;
|
|
70
|
+
/** Token counts, as the AI SDK and the chats say them. */
|
|
71
|
+
export type TurnUsage = {
|
|
72
|
+
inputTokens?: number;
|
|
73
|
+
outputTokens?: number;
|
|
74
|
+
promptTokens?: number;
|
|
75
|
+
completionTokens?: number;
|
|
76
|
+
};
|
|
77
|
+
/** End a turn: its answer, its tokens, or its error. */
|
|
78
|
+
export declare function endAgentTurn(span: OtelLiveSpan, outcome: {
|
|
79
|
+
text?: string;
|
|
80
|
+
usage?: TurnUsage;
|
|
81
|
+
error?: string;
|
|
82
|
+
}): void;
|
|
83
|
+
/** A tool call: its name, id, arguments, kind and marks. */
|
|
84
|
+
export declare function startToolCall(tracer: OtelLiveTracer, call: {
|
|
85
|
+
agent: string;
|
|
86
|
+
name: string;
|
|
87
|
+
id?: string;
|
|
88
|
+
args?: unknown;
|
|
89
|
+
tool: ToolClass;
|
|
90
|
+
/** The span it is part of: the agent's innermost open span unless said. */
|
|
91
|
+
parent?: OtelSpanContext;
|
|
92
|
+
/** How the page knows of it, when another process made it. */
|
|
93
|
+
origin?: string;
|
|
94
|
+
}): OtelLiveSpan;
|
|
95
|
+
/** End a tool call: its result, or its error. */
|
|
96
|
+
export declare function endToolCall(span: OtelLiveSpan, outcome: {
|
|
97
|
+
result?: unknown;
|
|
98
|
+
error?: string;
|
|
99
|
+
}): void;
|
|
100
|
+
/** Where a span comes from, for the Inspector's *Source* filter. */
|
|
101
|
+
export declare function sourceOfSpan(span: OtelSpan): string | undefined;
|
|
102
|
+
/** A source, in words. */
|
|
103
|
+
export declare const SPAN_SOURCE_LABELS: Record<string, string>;
|
|
104
|
+
/** One line that says what a span carried: a call's arguments, a turn's question and answer, a message's words. */
|
|
105
|
+
export declare function describeAgentSpan(span: OtelSpan): string | undefined;
|
|
106
|
+
/** `TASK_STATE_WORKING` is `working`. */
|
|
107
|
+
export declare function stateWord(state: unknown): string;
|
|
108
|
+
/** A tracer for a page, made once (or the one given). */
|
|
109
|
+
export declare function useAgentInspector(given?: OtelLiveTracer | null): {
|
|
110
|
+
tracer: OtelLiveTracer;
|
|
111
|
+
};
|
|
112
|
+
/** The tracer the chats under it record into. */
|
|
113
|
+
export declare const AgentInspectorContext: import("react").Context<OtelLiveTracer | null>;
|
|
114
|
+
/** The tracer of the nearest {@link AgentInspectorProvider}, if any. */
|
|
115
|
+
export declare function useAgentInspectorTracer(): OtelLiveTracer | null;
|
|
116
|
+
/** Every chat under it records what its agent does into `tracer`. */
|
|
117
|
+
export declare function AgentInspectorProvider({ tracer, children, }: {
|
|
118
|
+
tracer: OtelLiveTracer;
|
|
119
|
+
children?: ReactNode;
|
|
120
|
+
}): JSX.Element;
|