@elizaos/client 1.6.1-alpha.4 → 1.6.1-alpha.6
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/dist/assets/main-BM2lpId8.js +155 -0
- package/dist/assets/main-BM2lpId8.js.map +1 -0
- package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
- package/dist/assets/{main-C4q5_rtN.js → main-CQAV8tyh.js} +4 -4
- package/dist/assets/main-CQAV8tyh.js.map +1 -0
- package/dist/assets/react-vendor-C1OK-nqm.js +611 -0
- package/dist/assets/react-vendor-C1OK-nqm.js.map +1 -0
- package/dist/index.html +1 -1
- package/package.json +29 -25
- package/src/components/agent-prism/Avatar.tsx +164 -0
- package/src/components/agent-prism/Badge.tsx +109 -0
- package/src/components/agent-prism/Button.tsx +138 -0
- package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
- package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
- package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
- package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
- package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
- package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
- package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
- package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
- package/src/components/agent-prism/IconButton.tsx +75 -0
- package/src/components/agent-prism/PriceBadge.tsx +12 -0
- package/src/components/agent-prism/SearchInput.tsx +17 -0
- package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
- package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
- package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
- package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
- package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
- package/src/components/agent-prism/SpanStatus.tsx +79 -0
- package/src/components/agent-prism/Tabs.tsx +141 -0
- package/src/components/agent-prism/TextInput.tsx +142 -0
- package/src/components/agent-prism/TimestampBadge.tsx +28 -0
- package/src/components/agent-prism/TokensBadge.tsx +26 -0
- package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
- package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
- package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
- package/src/components/agent-prism/TraceViewer.tsx +476 -0
- package/src/components/agent-prism/TreeView.tsx +57 -0
- package/src/components/agent-prism/shared.ts +210 -0
- package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
- package/src/components/agent-sidebar.tsx +2 -2
- package/src/components/chat.tsx +8 -8
- package/src/lib/agent-prism-utils.ts +46 -0
- package/src/lib/eliza-span-adapter.ts +487 -0
- package/dist/assets/main-BNtEiK3o.js +0 -141
- package/dist/assets/main-BNtEiK3o.js.map +0 -1
- package/dist/assets/main-C4q5_rtN.js.map +0 -1
- package/dist/assets/react-vendor-pe76PXQl.js +0 -546
- package/dist/assets/react-vendor-pe76PXQl.js.map +0 -1
|
@@ -123,10 +123,10 @@ export function AgentSidebar({ agentId, agentName, channelId }: AgentSidebarProp
|
|
|
123
123
|
|
|
124
124
|
<TabsContent
|
|
125
125
|
value="timeline"
|
|
126
|
-
className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0"
|
|
126
|
+
className="overflow-y-auto overflow-x-hidden flex-1 w-full max-w-full min-h-0 p-0"
|
|
127
127
|
>
|
|
128
128
|
{detailsTab === 'timeline' && agentId && (
|
|
129
|
-
<div className="w-full max-w-full
|
|
129
|
+
<div className="w-full max-w-full h-full">
|
|
130
130
|
<AgentRunTimeline agentId={agentId} />
|
|
131
131
|
</div>
|
|
132
132
|
)}
|
package/src/components/chat.tsx
CHANGED
|
@@ -372,10 +372,10 @@ export default function Chat({
|
|
|
372
372
|
// Convert AgentWithStatus to Agent, ensuring required fields have defaults
|
|
373
373
|
const targetAgentData: Agent | undefined = agentDataResponse?.data
|
|
374
374
|
? ({
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
...agentDataResponse.data,
|
|
376
|
+
createdAt: agentDataResponse.data.createdAt || Date.now(),
|
|
377
|
+
updatedAt: agentDataResponse.data.updatedAt || Date.now(),
|
|
378
|
+
} as Agent)
|
|
379
379
|
: undefined;
|
|
380
380
|
|
|
381
381
|
const { handleDelete: handleDeleteAgent, isDeleting: isDeletingAgent } = useDeleteAgent(
|
|
@@ -1360,11 +1360,11 @@ export default function Chat({
|
|
|
1360
1360
|
<span className="text-xs text-muted-foreground">
|
|
1361
1361
|
{moment(
|
|
1362
1362
|
(typeof channel.metadata?.createdAt === 'string' ||
|
|
1363
|
-
|
|
1363
|
+
typeof channel.metadata?.createdAt === 'number'
|
|
1364
1364
|
? channel.metadata.createdAt
|
|
1365
1365
|
: null) ||
|
|
1366
|
-
|
|
1367
|
-
|
|
1366
|
+
channel.updatedAt ||
|
|
1367
|
+
channel.createdAt
|
|
1368
1368
|
).fromNow()}
|
|
1369
1369
|
</span>
|
|
1370
1370
|
</div>
|
|
@@ -1773,7 +1773,7 @@ export default function Chat({
|
|
|
1773
1773
|
!chatState.isMobile && (
|
|
1774
1774
|
<>
|
|
1775
1775
|
<ResizableHandle withHandle />
|
|
1776
|
-
<ResizablePanel defaultSize={sidebarPanelSize} minSize={
|
|
1776
|
+
<ResizablePanel defaultSize={sidebarPanelSize} minSize={15} maxSize={85}>
|
|
1777
1777
|
<AgentSidebar
|
|
1778
1778
|
agentId={sidebarAgentId}
|
|
1779
1779
|
agentName={sidebarAgentName}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TraceSpan } from '@evilmartians/agent-prism-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively filter spans based on a search query
|
|
5
|
+
* Matches against span title, type, and attributes
|
|
6
|
+
*/
|
|
7
|
+
export function filterSpansRecursively(
|
|
8
|
+
spans: TraceSpan[],
|
|
9
|
+
searchQuery: string
|
|
10
|
+
): TraceSpan[] {
|
|
11
|
+
const query = searchQuery.toLowerCase().trim();
|
|
12
|
+
if (!query) {
|
|
13
|
+
return spans;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const filtered: TraceSpan[] = [];
|
|
17
|
+
|
|
18
|
+
for (const span of spans) {
|
|
19
|
+
// Check if current span matches
|
|
20
|
+
const titleMatch = span.title.toLowerCase().includes(query);
|
|
21
|
+
const typeMatch = span.type.toLowerCase().includes(query);
|
|
22
|
+
const attributeMatch = span.attributes?.some(
|
|
23
|
+
(attr) =>
|
|
24
|
+
attr.key.toLowerCase().includes(query) ||
|
|
25
|
+
attr.value.stringValue?.toLowerCase().includes(query) ||
|
|
26
|
+
attr.value.intValue?.toLowerCase().includes(query)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const matchesQuery = titleMatch || typeMatch || attributeMatch;
|
|
30
|
+
|
|
31
|
+
// Recursively filter children
|
|
32
|
+
const filteredChildren = span.children
|
|
33
|
+
? filterSpansRecursively(span.children, searchQuery)
|
|
34
|
+
: [];
|
|
35
|
+
|
|
36
|
+
// Include span if it matches or if any of its children match
|
|
37
|
+
if (matchesQuery || filteredChildren.length > 0) {
|
|
38
|
+
filtered.push({
|
|
39
|
+
...span,
|
|
40
|
+
children: filteredChildren.length > 0 ? filteredChildren : span.children,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return filtered;
|
|
46
|
+
}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TraceSpan,
|
|
3
|
+
TraceSpanCategory,
|
|
4
|
+
TraceSpanStatus,
|
|
5
|
+
TraceSpanAttribute,
|
|
6
|
+
InputOutputData,
|
|
7
|
+
TraceRecord,
|
|
8
|
+
} from '@evilmartians/agent-prism-types';
|
|
9
|
+
import type { RunDetail, RunEvent, RunSummary } from '@elizaos/api-client';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Adapter to convert ElizaOS RunDetail data to Agent Prism TraceSpan format
|
|
13
|
+
*/
|
|
14
|
+
export class ElizaSpanAdapter {
|
|
15
|
+
/**
|
|
16
|
+
* Convert ElizaOS RunDetail to Agent Prism TraceSpans with hierarchical structure
|
|
17
|
+
*/
|
|
18
|
+
convertRunDetailToTraceSpans(runDetail: RunDetail): TraceSpan[] {
|
|
19
|
+
const events = runDetail.events;
|
|
20
|
+
if (!events || events.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Sort events by timestamp
|
|
25
|
+
const sortedEvents = [...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
26
|
+
|
|
27
|
+
// Track actions and their attempts
|
|
28
|
+
const actionMap = new Map<string, TraceSpan>();
|
|
29
|
+
const attemptMap = new Map<string, TraceSpan>();
|
|
30
|
+
const rootSpans: TraceSpan[] = [];
|
|
31
|
+
|
|
32
|
+
sortedEvents.forEach((event, index) => {
|
|
33
|
+
switch (event.type) {
|
|
34
|
+
case 'RUN_STARTED': {
|
|
35
|
+
// Create root run span
|
|
36
|
+
const runSpan = this.createRunSpan(runDetail, event);
|
|
37
|
+
rootSpans.push(runSpan);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'ACTION_STARTED': {
|
|
42
|
+
const actionName =
|
|
43
|
+
(event.data.actionName as string) ||
|
|
44
|
+
(event.data.actionId as string) ||
|
|
45
|
+
`Action ${index}`;
|
|
46
|
+
const actionKey = (event.data.actionId as string) || actionName;
|
|
47
|
+
|
|
48
|
+
let actionSpan = actionMap.get(actionKey);
|
|
49
|
+
if (!actionSpan) {
|
|
50
|
+
// Create new action span
|
|
51
|
+
actionSpan = {
|
|
52
|
+
id: `action-${actionKey}`,
|
|
53
|
+
title: actionName,
|
|
54
|
+
type: 'agent_invocation' as TraceSpanCategory,
|
|
55
|
+
status: 'pending' as TraceSpanStatus,
|
|
56
|
+
startTime: new Date(event.timestamp),
|
|
57
|
+
endTime: new Date(event.timestamp), // Will be updated on completion
|
|
58
|
+
duration: 0,
|
|
59
|
+
raw: JSON.stringify(event, null, 2),
|
|
60
|
+
attributes: this.convertEventDataToAttributes(event.data),
|
|
61
|
+
children: [],
|
|
62
|
+
};
|
|
63
|
+
actionMap.set(actionKey, actionSpan);
|
|
64
|
+
rootSpans.push(actionSpan);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create attempt span
|
|
68
|
+
const attemptIndex = (actionSpan.children?.length || 0) + 1;
|
|
69
|
+
const attemptSpan: TraceSpan = {
|
|
70
|
+
id: `attempt-${actionKey}-${attemptIndex}`,
|
|
71
|
+
title: `Attempt ${attemptIndex}`,
|
|
72
|
+
type: 'span' as TraceSpanCategory,
|
|
73
|
+
status: 'pending' as TraceSpanStatus,
|
|
74
|
+
startTime: new Date(event.timestamp),
|
|
75
|
+
endTime: new Date(event.timestamp),
|
|
76
|
+
duration: 0,
|
|
77
|
+
raw: JSON.stringify(event, null, 2),
|
|
78
|
+
attributes: this.convertEventDataToAttributes(event.data),
|
|
79
|
+
children: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
actionSpan.children = [...(actionSpan.children || []), attemptSpan];
|
|
83
|
+
attemptMap.set(actionKey, attemptSpan);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'ACTION_COMPLETED': {
|
|
88
|
+
const actionName =
|
|
89
|
+
(event.data.actionName as string) ||
|
|
90
|
+
(event.data.actionId as string) ||
|
|
91
|
+
`Action ${index}`;
|
|
92
|
+
const actionKey = (event.data.actionId as string) || actionName;
|
|
93
|
+
const actionSpan = actionMap.get(actionKey);
|
|
94
|
+
const attemptSpan = attemptMap.get(actionKey);
|
|
95
|
+
|
|
96
|
+
// Extract input/output if available
|
|
97
|
+
const prompt = this.extractPrompt(event.data);
|
|
98
|
+
const response = this.extractResponse(event.data);
|
|
99
|
+
|
|
100
|
+
if (attemptSpan) {
|
|
101
|
+
const success = (event.data.success as boolean | undefined) !== false;
|
|
102
|
+
attemptSpan.status = success ? 'success' : 'error';
|
|
103
|
+
attemptSpan.endTime = new Date(event.timestamp);
|
|
104
|
+
attemptSpan.duration = event.timestamp - attemptSpan.startTime.getTime();
|
|
105
|
+
if (prompt) attemptSpan.input = prompt;
|
|
106
|
+
if (response) attemptSpan.output = response;
|
|
107
|
+
attemptMap.delete(actionKey);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (actionSpan) {
|
|
111
|
+
const success = (event.data.success as boolean | undefined) !== false;
|
|
112
|
+
actionSpan.status = success ? 'success' : 'error';
|
|
113
|
+
actionSpan.endTime = new Date(event.timestamp);
|
|
114
|
+
actionSpan.duration =
|
|
115
|
+
event.timestamp - actionSpan.startTime.getTime();
|
|
116
|
+
if (prompt && !actionSpan.input) actionSpan.input = prompt;
|
|
117
|
+
if (response && !actionSpan.output) actionSpan.output = response;
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'MODEL_USED': {
|
|
123
|
+
const modelType = (event.data.modelType as string) || 'Model Call';
|
|
124
|
+
|
|
125
|
+
// Extract prompt and response from event data
|
|
126
|
+
const prompt = this.extractPrompt(event.data);
|
|
127
|
+
const response = this.extractResponse(event.data);
|
|
128
|
+
|
|
129
|
+
const modelSpan: TraceSpan = {
|
|
130
|
+
id: `model-${index}`,
|
|
131
|
+
title: modelType,
|
|
132
|
+
type: 'llm_call' as TraceSpanCategory,
|
|
133
|
+
status: 'success' as TraceSpanStatus,
|
|
134
|
+
startTime: new Date(event.timestamp),
|
|
135
|
+
endTime: new Date(
|
|
136
|
+
event.timestamp + ((event.data.executionTime as number) || 0)
|
|
137
|
+
),
|
|
138
|
+
duration: (event.data.executionTime as number) || 0,
|
|
139
|
+
raw: JSON.stringify(event, null, 2),
|
|
140
|
+
attributes: this.convertEventDataToAttributes(event.data),
|
|
141
|
+
input: prompt,
|
|
142
|
+
output: response,
|
|
143
|
+
tokensCount: this.extractTokensCount(event.data),
|
|
144
|
+
cost: this.extractCost(event.data),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Attach to current attempt or action
|
|
148
|
+
const actionContext = (event.data.actionContext as string | undefined) || undefined;
|
|
149
|
+
const targetKey = actionContext || Array.from(attemptMap.keys()).pop();
|
|
150
|
+
|
|
151
|
+
if (targetKey) {
|
|
152
|
+
const attemptSpan = attemptMap.get(targetKey);
|
|
153
|
+
if (attemptSpan) {
|
|
154
|
+
attemptSpan.children = [...(attemptSpan.children || []), modelSpan];
|
|
155
|
+
} else {
|
|
156
|
+
// Fallback to action
|
|
157
|
+
const actionSpan = actionMap.get(targetKey);
|
|
158
|
+
if (actionSpan && actionSpan.children && actionSpan.children.length > 0) {
|
|
159
|
+
const lastAttempt = actionSpan.children[actionSpan.children.length - 1];
|
|
160
|
+
lastAttempt.children = [...(lastAttempt.children || []), modelSpan];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
rootSpans.push(modelSpan);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'EVALUATOR_COMPLETED': {
|
|
170
|
+
const evaluatorName = (event.data.evaluatorName as string) || `Evaluator ${index}`;
|
|
171
|
+
const evaluatorSpan: TraceSpan = {
|
|
172
|
+
id: `evaluator-${index}`,
|
|
173
|
+
title: evaluatorName,
|
|
174
|
+
type: 'chain_operation' as TraceSpanCategory,
|
|
175
|
+
status: 'success' as TraceSpanStatus,
|
|
176
|
+
startTime: new Date(event.timestamp),
|
|
177
|
+
endTime: new Date(event.timestamp),
|
|
178
|
+
duration: 0,
|
|
179
|
+
raw: JSON.stringify(event, null, 2),
|
|
180
|
+
attributes: this.convertEventDataToAttributes(event.data),
|
|
181
|
+
};
|
|
182
|
+
rootSpans.push(evaluatorSpan);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case 'EMBEDDING_EVENT': {
|
|
187
|
+
const status = (event.data.status as string) || 'completed';
|
|
188
|
+
const embeddingSpan: TraceSpan = {
|
|
189
|
+
id: `embedding-${index}`,
|
|
190
|
+
title: `Embedding ${status}`,
|
|
191
|
+
type: 'embedding' as TraceSpanCategory,
|
|
192
|
+
status: status === 'failed' ? 'error' : 'success',
|
|
193
|
+
startTime: new Date(event.timestamp),
|
|
194
|
+
endTime: new Date(
|
|
195
|
+
event.timestamp + ((event.data.durationMs as number) || 0)
|
|
196
|
+
),
|
|
197
|
+
duration: (event.data.durationMs as number) || 0,
|
|
198
|
+
raw: JSON.stringify(event, null, 2),
|
|
199
|
+
attributes: this.convertEventDataToAttributes(event.data),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Attach to current attempt or action
|
|
203
|
+
const targetKey = Array.from(attemptMap.keys()).pop();
|
|
204
|
+
if (targetKey) {
|
|
205
|
+
const attemptSpan = attemptMap.get(targetKey);
|
|
206
|
+
if (attemptSpan) {
|
|
207
|
+
attemptSpan.children = [...(attemptSpan.children || []), embeddingSpan];
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
rootSpans.push(embeddingSpan);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
default:
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return rootSpans;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a root run span from RunDetail
|
|
225
|
+
*/
|
|
226
|
+
private createRunSpan(runDetail: RunDetail, startEvent: RunEvent): TraceSpan {
|
|
227
|
+
const summary = runDetail.summary;
|
|
228
|
+
const duration = summary.durationMs || 0;
|
|
229
|
+
const startTime = new Date(startEvent.timestamp);
|
|
230
|
+
const endTime = new Date(startEvent.timestamp + duration);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id: summary.runId,
|
|
234
|
+
title: `Run ${new Date(summary.startedAt || Date.now()).toLocaleTimeString()}`,
|
|
235
|
+
type: 'agent_invocation' as TraceSpanCategory,
|
|
236
|
+
status: this.convertRunStatus(summary.status),
|
|
237
|
+
startTime,
|
|
238
|
+
endTime,
|
|
239
|
+
duration,
|
|
240
|
+
raw: JSON.stringify(runDetail, null, 2),
|
|
241
|
+
attributes: [
|
|
242
|
+
{ key: 'run.id', value: { stringValue: summary.runId } },
|
|
243
|
+
{ key: 'run.status', value: { stringValue: summary.status } },
|
|
244
|
+
...(summary.messageId
|
|
245
|
+
? [{ key: 'message.id', value: { stringValue: summary.messageId } }]
|
|
246
|
+
: []),
|
|
247
|
+
...(summary.roomId ? [{ key: 'room.id', value: { stringValue: summary.roomId } }] : []),
|
|
248
|
+
] as TraceSpanAttribute[],
|
|
249
|
+
children: [],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Convert RunStatus to TraceSpanStatus
|
|
255
|
+
*/
|
|
256
|
+
private convertRunStatus(status: string): TraceSpanStatus {
|
|
257
|
+
switch (status) {
|
|
258
|
+
case 'completed':
|
|
259
|
+
return 'success';
|
|
260
|
+
case 'error':
|
|
261
|
+
return 'error';
|
|
262
|
+
case 'timeout':
|
|
263
|
+
return 'warning';
|
|
264
|
+
case 'started':
|
|
265
|
+
return 'pending';
|
|
266
|
+
default:
|
|
267
|
+
return 'pending';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Convert event data to TraceSpanAttribute array
|
|
273
|
+
*/
|
|
274
|
+
private convertEventDataToAttributes(data: Record<string, unknown>): TraceSpanAttribute[] {
|
|
275
|
+
return Object.entries(data).map(([key, value]) => ({
|
|
276
|
+
key,
|
|
277
|
+
value: this.convertValueToAttributeValue(value),
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Convert a value to TraceSpanAttributeValue
|
|
283
|
+
*/
|
|
284
|
+
private convertValueToAttributeValue(value: unknown): {
|
|
285
|
+
stringValue?: string;
|
|
286
|
+
intValue?: string;
|
|
287
|
+
boolValue?: boolean;
|
|
288
|
+
} {
|
|
289
|
+
if (typeof value === 'string') {
|
|
290
|
+
return { stringValue: value };
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === 'number') {
|
|
293
|
+
return { intValue: value.toString() };
|
|
294
|
+
}
|
|
295
|
+
if (typeof value === 'boolean') {
|
|
296
|
+
return { boolValue: value };
|
|
297
|
+
}
|
|
298
|
+
return { stringValue: JSON.stringify(value) };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Safely coerce a possibly numeric value (number or numeric string) to number
|
|
303
|
+
*/
|
|
304
|
+
private coerceToNumber(value: unknown): number | undefined {
|
|
305
|
+
if (typeof value === 'number') {
|
|
306
|
+
return Number.isFinite(value) ? value : undefined;
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === 'string') {
|
|
309
|
+
const parsed = Number(value);
|
|
310
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Extract tokens count from event data
|
|
317
|
+
*/
|
|
318
|
+
private extractTokensCount(data: Record<string, unknown>): number | undefined {
|
|
319
|
+
// Prefer explicit direct fields if present, even if they sum to 0
|
|
320
|
+
const hasInputTokens = Object.prototype.hasOwnProperty.call(data, 'inputTokens');
|
|
321
|
+
const hasOutputTokens = Object.prototype.hasOwnProperty.call(data, 'outputTokens');
|
|
322
|
+
if (hasInputTokens || hasOutputTokens) {
|
|
323
|
+
const input = this.coerceToNumber((data as Record<string, unknown>)['inputTokens']) ?? 0;
|
|
324
|
+
const output = this.coerceToNumber((data as Record<string, unknown>)['outputTokens']) ?? 0;
|
|
325
|
+
return input + output;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Helper to extract from a usage-like object
|
|
329
|
+
const extractFromUsage = (usageContainer: unknown): number | undefined => {
|
|
330
|
+
if (!usageContainer || typeof usageContainer !== 'object') return undefined;
|
|
331
|
+
const container = usageContainer as Record<string, unknown>;
|
|
332
|
+
const totalTokens = this.coerceToNumber(container['total_tokens']);
|
|
333
|
+
if (totalTokens !== undefined) return totalTokens;
|
|
334
|
+
const hasPrompt = Object.prototype.hasOwnProperty.call(container, 'prompt_tokens');
|
|
335
|
+
const hasCompletion = Object.prototype.hasOwnProperty.call(container, 'completion_tokens');
|
|
336
|
+
if (hasPrompt || hasCompletion) {
|
|
337
|
+
const prompt = this.coerceToNumber(container['prompt_tokens']) ?? 0;
|
|
338
|
+
const completion = this.coerceToNumber(container['completion_tokens']) ?? 0;
|
|
339
|
+
return prompt + completion;
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Try response.usage object (common in LLM responses)
|
|
345
|
+
if (data.response && typeof data.response === 'object') {
|
|
346
|
+
const response = data.response as Record<string, unknown>;
|
|
347
|
+
const fromResponseUsage = extractFromUsage(response['usage']);
|
|
348
|
+
if (fromResponseUsage !== undefined) return fromResponseUsage;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Try top-level usage object
|
|
352
|
+
const fromTopLevelUsage = extractFromUsage(data['usage']);
|
|
353
|
+
if (fromTopLevelUsage !== undefined) return fromTopLevelUsage;
|
|
354
|
+
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Extract cost from event data
|
|
360
|
+
*/
|
|
361
|
+
private extractCost(data: Record<string, unknown>): number | undefined {
|
|
362
|
+
// Try direct cost field
|
|
363
|
+
if (data.cost && typeof data.cost === 'number') {
|
|
364
|
+
return data.cost;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Try response.cost
|
|
368
|
+
if (data.response && typeof data.response === 'object') {
|
|
369
|
+
const response = data.response as Record<string, unknown>;
|
|
370
|
+
if (response.cost && typeof response.cost === 'number') {
|
|
371
|
+
return response.cost;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Extract prompt/input from event data
|
|
380
|
+
*/
|
|
381
|
+
private extractPrompt(data: Record<string, unknown>): string | undefined {
|
|
382
|
+
// Handle multiple prompts array (from actions)
|
|
383
|
+
if (data.prompts && Array.isArray(data.prompts)) {
|
|
384
|
+
const prompts = data.prompts as Array<{ prompt?: string; modelType?: string }>;
|
|
385
|
+
if (prompts.length > 0) {
|
|
386
|
+
return prompts
|
|
387
|
+
.map((p, idx) => {
|
|
388
|
+
const header = prompts.length > 1 ? `[Prompt ${idx + 1}${p.modelType ? ` - ${p.modelType}` : ''}]\n` : '';
|
|
389
|
+
return header + (p.prompt || '');
|
|
390
|
+
})
|
|
391
|
+
.join('\n\n---\n\n');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Try direct prompt field
|
|
396
|
+
if (data.prompt && typeof data.prompt === 'string') {
|
|
397
|
+
return data.prompt;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Try params.prompt
|
|
401
|
+
if (data.params && typeof data.params === 'object') {
|
|
402
|
+
const params = data.params as Record<string, unknown>;
|
|
403
|
+
if (params.prompt && typeof params.prompt === 'string') {
|
|
404
|
+
return params.prompt;
|
|
405
|
+
}
|
|
406
|
+
// Return formatted params if no specific prompt
|
|
407
|
+
const { prompt: _, ...otherParams } = params;
|
|
408
|
+
if (Object.keys(otherParams).length > 0) {
|
|
409
|
+
return JSON.stringify(otherParams, null, 2);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Try input field
|
|
414
|
+
if (data.input && typeof data.input === 'string') {
|
|
415
|
+
return data.input;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Extract response/output from event data
|
|
423
|
+
*/
|
|
424
|
+
private extractResponse(data: Record<string, unknown>): string | undefined {
|
|
425
|
+
// Handle response object
|
|
426
|
+
if (data.response) {
|
|
427
|
+
if (typeof data.response === 'string') {
|
|
428
|
+
return data.response;
|
|
429
|
+
}
|
|
430
|
+
if (typeof data.response === 'object') {
|
|
431
|
+
const response = data.response as Record<string, unknown>;
|
|
432
|
+
|
|
433
|
+
// Extract text content from common response structures
|
|
434
|
+
if (response.content && typeof response.content === 'string') {
|
|
435
|
+
return response.content;
|
|
436
|
+
}
|
|
437
|
+
if (response.text && typeof response.text === 'string') {
|
|
438
|
+
return response.text;
|
|
439
|
+
}
|
|
440
|
+
if (response.message && typeof response.message === 'string') {
|
|
441
|
+
return response.message;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Format the full response
|
|
445
|
+
return JSON.stringify(response, null, 2);
|
|
446
|
+
}
|
|
447
|
+
return String(data.response);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Try output field
|
|
451
|
+
if (data.output) {
|
|
452
|
+
if (typeof data.output === 'string') {
|
|
453
|
+
return data.output;
|
|
454
|
+
}
|
|
455
|
+
return JSON.stringify(data.output, null, 2);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Try result field (for action results)
|
|
459
|
+
if (data.result) {
|
|
460
|
+
if (typeof data.result === 'string') {
|
|
461
|
+
return data.result;
|
|
462
|
+
}
|
|
463
|
+
return JSON.stringify(data.result, null, 2);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Convert RunSummary to TraceRecord for TraceList component
|
|
471
|
+
*/
|
|
472
|
+
convertRunSummaryToTraceRecord(summary: RunSummary): TraceRecord {
|
|
473
|
+
// Use first 8 characters of runId for compact display
|
|
474
|
+
const shortId = summary.runId.slice(0, 8);
|
|
475
|
+
return {
|
|
476
|
+
id: summary.runId,
|
|
477
|
+
name: `Run ${shortId}`,
|
|
478
|
+
spansCount: Object.values(summary.counts || {}).reduce((a, b) => a + b, 0),
|
|
479
|
+
durationMs: summary.durationMs || 0,
|
|
480
|
+
agentDescription: `Status: ${summary.status}`,
|
|
481
|
+
startTime: summary.startedAt || undefined,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Export a singleton instance
|
|
487
|
+
export const elizaSpanAdapter = new ElizaSpanAdapter();
|