@elizaos/client 1.6.1-alpha.5 → 1.6.1-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/assets/{main-Bbs84AcL.js → main-BM2lpId8.js} +60 -60
  2. package/dist/assets/main-BM2lpId8.js.map +1 -0
  3. package/dist/assets/{main-4tyUgNqd.js → main-CQAV8tyh.js} +4 -4
  4. package/dist/assets/main-CQAV8tyh.js.map +1 -0
  5. package/dist/assets/react-vendor-C1OK-nqm.js +611 -0
  6. package/dist/assets/react-vendor-C1OK-nqm.js.map +1 -0
  7. package/dist/index.html +1 -1
  8. package/package.json +25 -25
  9. package/src/components/agent-prism/Avatar.tsx +56 -68
  10. package/src/components/agent-prism/Badge.tsx +22 -29
  11. package/src/components/agent-prism/Button.tsx +39 -51
  12. package/src/components/agent-prism/CollapseAndExpandControls.tsx +9 -25
  13. package/src/components/agent-prism/CollapsibleSection.tsx +18 -18
  14. package/src/components/agent-prism/DetailsView/DetailsView.tsx +25 -30
  15. package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +6 -12
  16. package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +9 -13
  17. package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +2 -2
  18. package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +30 -60
  19. package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +10 -18
  20. package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +3 -3
  21. package/src/components/agent-prism/IconButton.tsx +25 -28
  22. package/src/components/agent-prism/PriceBadge.tsx +4 -4
  23. package/src/components/agent-prism/SearchInput.tsx +3 -9
  24. package/src/components/agent-prism/SpanCard/SpanCard.tsx +74 -104
  25. package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +7 -13
  26. package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +9 -9
  27. package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +15 -20
  28. package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +5 -9
  29. package/src/components/agent-prism/SpanStatus.tsx +24 -30
  30. package/src/components/agent-prism/Tabs.tsx +16 -19
  31. package/src/components/agent-prism/TextInput.tsx +18 -21
  32. package/src/components/agent-prism/TimestampBadge.tsx +5 -9
  33. package/src/components/agent-prism/TokensBadge.tsx +6 -10
  34. package/src/components/agent-prism/TraceList/TraceList.tsx +11 -17
  35. package/src/components/agent-prism/TraceList/TraceListItem.tsx +18 -24
  36. package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +8 -20
  37. package/src/components/agent-prism/TraceViewer.tsx +36 -53
  38. package/src/components/agent-prism/TreeView.tsx +7 -7
  39. package/src/components/agent-prism/shared.ts +81 -93
  40. package/src/components/agent-runs/AgentRunTimeline.tsx +3 -5
  41. package/src/components/chat.tsx +7 -7
  42. package/src/lib/agent-prism-utils.ts +29 -32
  43. package/src/lib/eliza-span-adapter.ts +438 -440
  44. package/dist/assets/main-4tyUgNqd.js.map +0 -1
  45. package/dist/assets/main-Bbs84AcL.js.map +0 -1
  46. package/dist/assets/react-vendor-DxnAFk-d.js +0 -611
  47. package/dist/assets/react-vendor-DxnAFk-d.js.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import type {
2
- TraceSpan,
3
- TraceSpanCategory,
4
- TraceSpanStatus,
5
- TraceSpanAttribute,
6
- InputOutputData,
7
- TraceRecord,
2
+ TraceSpan,
3
+ TraceSpanCategory,
4
+ TraceSpanStatus,
5
+ TraceSpanAttribute,
6
+ InputOutputData,
7
+ TraceRecord,
8
8
  } from '@evilmartians/agent-prism-types';
9
9
  import type { RunDetail, RunEvent, RunSummary } from '@elizaos/api-client';
10
10
 
@@ -12,475 +12,473 @@ import type { RunDetail, RunEvent, RunSummary } from '@elizaos/api-client';
12
12
  * Adapter to convert ElizaOS RunDetail data to Agent Prism TraceSpan format
13
13
  */
14
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
- };
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 [];
251
22
  }
252
23
 
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';
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;
268
39
  }
269
- }
270
40
 
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
- }
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
+ };
280
81
 
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 };
82
+ actionSpan.children = [...(actionSpan.children || []), attemptSpan];
83
+ attemptMap.set(actionKey, attemptSpan);
84
+ break;
297
85
  }
298
- return { stringValue: JSON.stringify(value) };
299
- }
300
86
 
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;
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 = event.timestamp - actionSpan.startTime.getTime();
115
+ if (prompt && !actionSpan.input) actionSpan.input = prompt;
116
+ if (response && !actionSpan.output) actionSpan.output = response;
117
+ }
118
+ break;
307
119
  }
308
- if (typeof value === 'string') {
309
- const parsed = Number(value);
310
- return Number.isFinite(parsed) ? parsed : undefined;
120
+
121
+ case 'MODEL_USED': {
122
+ const modelType = (event.data.modelType as string) || 'Model Call';
123
+
124
+ // Extract prompt and response from event data
125
+ const prompt = this.extractPrompt(event.data);
126
+ const response = this.extractResponse(event.data);
127
+
128
+ const modelSpan: TraceSpan = {
129
+ id: `model-${index}`,
130
+ title: modelType,
131
+ type: 'llm_call' as TraceSpanCategory,
132
+ status: 'success' as TraceSpanStatus,
133
+ startTime: new Date(event.timestamp),
134
+ endTime: new Date(event.timestamp + ((event.data.executionTime as number) || 0)),
135
+ duration: (event.data.executionTime as number) || 0,
136
+ raw: JSON.stringify(event, null, 2),
137
+ attributes: this.convertEventDataToAttributes(event.data),
138
+ input: prompt,
139
+ output: response,
140
+ tokensCount: this.extractTokensCount(event.data),
141
+ cost: this.extractCost(event.data),
142
+ };
143
+
144
+ // Attach to current attempt or action
145
+ const actionContext = (event.data.actionContext as string | undefined) || undefined;
146
+ const targetKey = actionContext || Array.from(attemptMap.keys()).pop();
147
+
148
+ if (targetKey) {
149
+ const attemptSpan = attemptMap.get(targetKey);
150
+ if (attemptSpan) {
151
+ attemptSpan.children = [...(attemptSpan.children || []), modelSpan];
152
+ } else {
153
+ // Fallback to action
154
+ const actionSpan = actionMap.get(targetKey);
155
+ if (actionSpan && actionSpan.children && actionSpan.children.length > 0) {
156
+ const lastAttempt = actionSpan.children[actionSpan.children.length - 1];
157
+ lastAttempt.children = [...(lastAttempt.children || []), modelSpan];
158
+ }
159
+ }
160
+ } else {
161
+ rootSpans.push(modelSpan);
162
+ }
163
+ break;
311
164
  }
312
- return undefined;
313
- }
314
165
 
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;
166
+ case 'EVALUATOR_COMPLETED': {
167
+ const evaluatorName = (event.data.evaluatorName as string) || `Evaluator ${index}`;
168
+ const evaluatorSpan: TraceSpan = {
169
+ id: `evaluator-${index}`,
170
+ title: evaluatorName,
171
+ type: 'chain_operation' as TraceSpanCategory,
172
+ status: 'success' as TraceSpanStatus,
173
+ startTime: new Date(event.timestamp),
174
+ endTime: new Date(event.timestamp),
175
+ duration: 0,
176
+ raw: JSON.stringify(event, null, 2),
177
+ attributes: this.convertEventDataToAttributes(event.data),
178
+ };
179
+ rootSpans.push(evaluatorSpan);
180
+ break;
326
181
  }
327
182
 
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;
183
+ case 'EMBEDDING_EVENT': {
184
+ const status = (event.data.status as string) || 'completed';
185
+ const embeddingSpan: TraceSpan = {
186
+ id: `embedding-${index}`,
187
+ title: `Embedding ${status}`,
188
+ type: 'embedding' as TraceSpanCategory,
189
+ status: status === 'failed' ? 'error' : 'success',
190
+ startTime: new Date(event.timestamp),
191
+ endTime: new Date(event.timestamp + ((event.data.durationMs as number) || 0)),
192
+ duration: (event.data.durationMs as number) || 0,
193
+ raw: JSON.stringify(event, null, 2),
194
+ attributes: this.convertEventDataToAttributes(event.data),
195
+ };
196
+
197
+ // Attach to current attempt or action
198
+ const targetKey = Array.from(attemptMap.keys()).pop();
199
+ if (targetKey) {
200
+ const attemptSpan = attemptMap.get(targetKey);
201
+ if (attemptSpan) {
202
+ attemptSpan.children = [...(attemptSpan.children || []), embeddingSpan];
340
203
  }
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;
204
+ } else {
205
+ rootSpans.push(embeddingSpan);
206
+ }
207
+ break;
349
208
  }
350
209
 
351
- // Try top-level usage object
352
- const fromTopLevelUsage = extractFromUsage(data['usage']);
353
- if (fromTopLevelUsage !== undefined) return fromTopLevelUsage;
210
+ default:
211
+ break;
212
+ }
213
+ });
214
+
215
+ return rootSpans;
216
+ }
217
+
218
+ /**
219
+ * Create a root run span from RunDetail
220
+ */
221
+ private createRunSpan(runDetail: RunDetail, startEvent: RunEvent): TraceSpan {
222
+ const summary = runDetail.summary;
223
+ const duration = summary.durationMs || 0;
224
+ const startTime = new Date(startEvent.timestamp);
225
+ const endTime = new Date(startEvent.timestamp + duration);
226
+
227
+ return {
228
+ id: summary.runId,
229
+ title: `Run ${new Date(summary.startedAt || Date.now()).toLocaleTimeString()}`,
230
+ type: 'agent_invocation' as TraceSpanCategory,
231
+ status: this.convertRunStatus(summary.status),
232
+ startTime,
233
+ endTime,
234
+ duration,
235
+ raw: JSON.stringify(runDetail, null, 2),
236
+ attributes: [
237
+ { key: 'run.id', value: { stringValue: summary.runId } },
238
+ { key: 'run.status', value: { stringValue: summary.status } },
239
+ ...(summary.messageId
240
+ ? [{ key: 'message.id', value: { stringValue: summary.messageId } }]
241
+ : []),
242
+ ...(summary.roomId ? [{ key: 'room.id', value: { stringValue: summary.roomId } }] : []),
243
+ ] as TraceSpanAttribute[],
244
+ children: [],
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Convert RunStatus to TraceSpanStatus
250
+ */
251
+ private convertRunStatus(status: string): TraceSpanStatus {
252
+ switch (status) {
253
+ case 'completed':
254
+ return 'success';
255
+ case 'error':
256
+ return 'error';
257
+ case 'timeout':
258
+ return 'warning';
259
+ case 'started':
260
+ return 'pending';
261
+ default:
262
+ return 'pending';
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Convert event data to TraceSpanAttribute array
268
+ */
269
+ private convertEventDataToAttributes(data: Record<string, unknown>): TraceSpanAttribute[] {
270
+ return Object.entries(data).map(([key, value]) => ({
271
+ key,
272
+ value: this.convertValueToAttributeValue(value),
273
+ }));
274
+ }
275
+
276
+ /**
277
+ * Convert a value to TraceSpanAttributeValue
278
+ */
279
+ private convertValueToAttributeValue(value: unknown): {
280
+ stringValue?: string;
281
+ intValue?: string;
282
+ boolValue?: boolean;
283
+ } {
284
+ if (typeof value === 'string') {
285
+ return { stringValue: value };
286
+ }
287
+ if (typeof value === 'number') {
288
+ return { intValue: value.toString() };
289
+ }
290
+ if (typeof value === 'boolean') {
291
+ return { boolValue: value };
292
+ }
293
+ return { stringValue: JSON.stringify(value) };
294
+ }
295
+
296
+ /**
297
+ * Safely coerce a possibly numeric value (number or numeric string) to number
298
+ */
299
+ private coerceToNumber(value: unknown): number | undefined {
300
+ if (typeof value === 'number') {
301
+ return Number.isFinite(value) ? value : undefined;
302
+ }
303
+ if (typeof value === 'string') {
304
+ const parsed = Number(value);
305
+ return Number.isFinite(parsed) ? parsed : undefined;
306
+ }
307
+ return undefined;
308
+ }
309
+
310
+ /**
311
+ * Extract tokens count from event data
312
+ */
313
+ private extractTokensCount(data: Record<string, unknown>): number | undefined {
314
+ // Prefer explicit direct fields if present, even if they sum to 0
315
+ const hasInputTokens = Object.prototype.hasOwnProperty.call(data, 'inputTokens');
316
+ const hasOutputTokens = Object.prototype.hasOwnProperty.call(data, 'outputTokens');
317
+ if (hasInputTokens || hasOutputTokens) {
318
+ const input = this.coerceToNumber((data as Record<string, unknown>)['inputTokens']) ?? 0;
319
+ const output = this.coerceToNumber((data as Record<string, unknown>)['outputTokens']) ?? 0;
320
+ return input + output;
321
+ }
354
322
 
355
- return undefined;
323
+ // Helper to extract from a usage-like object
324
+ const extractFromUsage = (usageContainer: unknown): number | undefined => {
325
+ if (!usageContainer || typeof usageContainer !== 'object') return undefined;
326
+ const container = usageContainer as Record<string, unknown>;
327
+ const totalTokens = this.coerceToNumber(container['total_tokens']);
328
+ if (totalTokens !== undefined) return totalTokens;
329
+ const hasPrompt = Object.prototype.hasOwnProperty.call(container, 'prompt_tokens');
330
+ const hasCompletion = Object.prototype.hasOwnProperty.call(container, 'completion_tokens');
331
+ if (hasPrompt || hasCompletion) {
332
+ const prompt = this.coerceToNumber(container['prompt_tokens']) ?? 0;
333
+ const completion = this.coerceToNumber(container['completion_tokens']) ?? 0;
334
+ return prompt + completion;
335
+ }
336
+ return undefined;
337
+ };
338
+
339
+ // Try response.usage object (common in LLM responses)
340
+ if (data.response && typeof data.response === 'object') {
341
+ const response = data.response as Record<string, unknown>;
342
+ const fromResponseUsage = extractFromUsage(response['usage']);
343
+ if (fromResponseUsage !== undefined) return fromResponseUsage;
356
344
  }
357
345
 
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
- }
346
+ // Try top-level usage object
347
+ const fromTopLevelUsage = extractFromUsage(data['usage']);
348
+ if (fromTopLevelUsage !== undefined) return fromTopLevelUsage;
366
349
 
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
- }
350
+ return undefined;
351
+ }
374
352
 
375
- return undefined;
353
+ /**
354
+ * Extract cost from event data
355
+ */
356
+ private extractCost(data: Record<string, unknown>): number | undefined {
357
+ // Try direct cost field
358
+ if (data.cost && typeof data.cost === 'number') {
359
+ return data.cost;
376
360
  }
377
361
 
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
- }
362
+ // Try response.cost
363
+ if (data.response && typeof data.response === 'object') {
364
+ const response = data.response as Record<string, unknown>;
365
+ if (response.cost && typeof response.cost === 'number') {
366
+ return response.cost;
367
+ }
368
+ }
394
369
 
395
- // Try direct prompt field
396
- if (data.prompt && typeof data.prompt === 'string') {
397
- return data.prompt;
398
- }
370
+ return undefined;
371
+ }
372
+
373
+ /**
374
+ * Extract prompt/input from event data
375
+ */
376
+ private extractPrompt(data: Record<string, unknown>): string | undefined {
377
+ // Handle multiple prompts array (from actions)
378
+ if (data.prompts && Array.isArray(data.prompts)) {
379
+ const prompts = data.prompts as Array<{ prompt?: string; modelType?: string }>;
380
+ if (prompts.length > 0) {
381
+ return prompts
382
+ .map((p, idx) => {
383
+ const header =
384
+ prompts.length > 1
385
+ ? `[Prompt ${idx + 1}${p.modelType ? ` - ${p.modelType}` : ''}]\n`
386
+ : '';
387
+ return header + (p.prompt || '');
388
+ })
389
+ .join('\n\n---\n\n');
390
+ }
391
+ }
399
392
 
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
- }
393
+ // Try direct prompt field
394
+ if (data.prompt && typeof data.prompt === 'string') {
395
+ return data.prompt;
396
+ }
412
397
 
413
- // Try input field
414
- if (data.input && typeof data.input === 'string') {
415
- return data.input;
416
- }
398
+ // Try params.prompt
399
+ if (data.params && typeof data.params === 'object') {
400
+ const params = data.params as Record<string, unknown>;
401
+ if (params.prompt && typeof params.prompt === 'string') {
402
+ return params.prompt;
403
+ }
404
+ // Return formatted params if no specific prompt
405
+ const { prompt: _, ...otherParams } = params;
406
+ if (Object.keys(otherParams).length > 0) {
407
+ return JSON.stringify(otherParams, null, 2);
408
+ }
409
+ }
417
410
 
418
- return undefined;
411
+ // Try input field
412
+ if (data.input && typeof data.input === 'string') {
413
+ return data.input;
419
414
  }
420
415
 
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);
416
+ return undefined;
417
+ }
418
+
419
+ /**
420
+ * Extract response/output from event data
421
+ */
422
+ private extractResponse(data: Record<string, unknown>): string | undefined {
423
+ // Handle response object
424
+ if (data.response) {
425
+ if (typeof data.response === 'string') {
426
+ return data.response;
427
+ }
428
+ if (typeof data.response === 'object') {
429
+ const response = data.response as Record<string, unknown>;
430
+
431
+ // Extract text content from common response structures
432
+ if (response.content && typeof response.content === 'string') {
433
+ return response.content;
448
434
  }
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);
435
+ if (response.text && typeof response.text === 'string') {
436
+ return response.text;
456
437
  }
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);
438
+ if (response.message && typeof response.message === 'string') {
439
+ return response.message;
464
440
  }
465
441
 
466
- return undefined;
442
+ // Format the full response
443
+ return JSON.stringify(response, null, 2);
444
+ }
445
+ return String(data.response);
467
446
  }
468
447
 
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
- };
448
+ // Try output field
449
+ if (data.output) {
450
+ if (typeof data.output === 'string') {
451
+ return data.output;
452
+ }
453
+ return JSON.stringify(data.output, null, 2);
483
454
  }
455
+
456
+ // Try result field (for action results)
457
+ if (data.result) {
458
+ if (typeof data.result === 'string') {
459
+ return data.result;
460
+ }
461
+ return JSON.stringify(data.result, null, 2);
462
+ }
463
+
464
+ return undefined;
465
+ }
466
+
467
+ /**
468
+ * Convert RunSummary to TraceRecord for TraceList component
469
+ */
470
+ convertRunSummaryToTraceRecord(summary: RunSummary): TraceRecord {
471
+ // Use first 8 characters of runId for compact display
472
+ const shortId = summary.runId.slice(0, 8);
473
+ return {
474
+ id: summary.runId,
475
+ name: `Run ${shortId}`,
476
+ spansCount: Object.values(summary.counts || {}).reduce((a, b) => a + b, 0),
477
+ durationMs: summary.durationMs || 0,
478
+ agentDescription: `Status: ${summary.status}`,
479
+ startTime: summary.startedAt || undefined,
480
+ };
481
+ }
484
482
  }
485
483
 
486
484
  // Export a singleton instance