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

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 (48) hide show
  1. package/dist/assets/{main-C4q5_rtN.js → main-4tyUgNqd.js} +3 -3
  2. package/dist/assets/{main-C4q5_rtN.js.map → main-4tyUgNqd.js.map} +1 -1
  3. package/dist/assets/{main-BNtEiK3o.js → main-Bbs84AcL.js} +77 -63
  4. package/dist/assets/main-Bbs84AcL.js.map +1 -0
  5. package/dist/assets/{main-BOBWcKWW.css → main-CNv6B3RZ.css} +597 -71
  6. package/dist/assets/react-vendor-DxnAFk-d.js +611 -0
  7. package/dist/assets/react-vendor-DxnAFk-d.js.map +1 -0
  8. package/dist/index.html +1 -1
  9. package/package.json +8 -4
  10. package/src/components/agent-prism/Avatar.tsx +164 -0
  11. package/src/components/agent-prism/Badge.tsx +109 -0
  12. package/src/components/agent-prism/Button.tsx +138 -0
  13. package/src/components/agent-prism/CollapseAndExpandControls.tsx +45 -0
  14. package/src/components/agent-prism/CollapsibleSection.tsx +121 -0
  15. package/src/components/agent-prism/DetailsView/DetailsView.tsx +141 -0
  16. package/src/components/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +45 -0
  17. package/src/components/agent-prism/DetailsView/DetailsViewHeader.tsx +77 -0
  18. package/src/components/agent-prism/DetailsView/DetailsViewHeaderActions.tsx +21 -0
  19. package/src/components/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +210 -0
  20. package/src/components/agent-prism/DetailsView/DetailsViewMetrics.tsx +53 -0
  21. package/src/components/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +24 -0
  22. package/src/components/agent-prism/IconButton.tsx +75 -0
  23. package/src/components/agent-prism/PriceBadge.tsx +12 -0
  24. package/src/components/agent-prism/SearchInput.tsx +17 -0
  25. package/src/components/agent-prism/SpanCard/SpanCard.tsx +467 -0
  26. package/src/components/agent-prism/SpanCard/SpanCardBadges.tsx +35 -0
  27. package/src/components/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
  28. package/src/components/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
  29. package/src/components/agent-prism/SpanCard/SpanCardToggle.tsx +32 -0
  30. package/src/components/agent-prism/SpanStatus.tsx +79 -0
  31. package/src/components/agent-prism/Tabs.tsx +141 -0
  32. package/src/components/agent-prism/TextInput.tsx +142 -0
  33. package/src/components/agent-prism/TimestampBadge.tsx +28 -0
  34. package/src/components/agent-prism/TokensBadge.tsx +26 -0
  35. package/src/components/agent-prism/TraceList/TraceList.tsx +80 -0
  36. package/src/components/agent-prism/TraceList/TraceListItem.tsx +79 -0
  37. package/src/components/agent-prism/TraceList/TraceListItemHeader.tsx +46 -0
  38. package/src/components/agent-prism/TraceViewer.tsx +476 -0
  39. package/src/components/agent-prism/TreeView.tsx +57 -0
  40. package/src/components/agent-prism/shared.ts +210 -0
  41. package/src/components/agent-runs/AgentRunTimeline.tsx +64 -673
  42. package/src/components/agent-sidebar.tsx +2 -2
  43. package/src/components/chat.tsx +8 -8
  44. package/src/lib/agent-prism-utils.ts +46 -0
  45. package/src/lib/eliza-span-adapter.ts +487 -0
  46. package/dist/assets/main-BNtEiK3o.js.map +0 -1
  47. package/dist/assets/react-vendor-pe76PXQl.js +0 -546
  48. 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 p-4">
129
+ <div className="w-full max-w-full h-full">
130
130
  <AgentRunTimeline agentId={agentId} />
131
131
  </div>
132
132
  )}
@@ -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
- ...agentDataResponse.data,
376
- createdAt: agentDataResponse.data.createdAt || Date.now(),
377
- updatedAt: agentDataResponse.data.updatedAt || Date.now(),
378
- } as Agent)
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
- typeof channel.metadata?.createdAt === 'number'
1363
+ typeof channel.metadata?.createdAt === 'number'
1364
1364
  ? channel.metadata.createdAt
1365
1365
  : null) ||
1366
- channel.updatedAt ||
1367
- channel.createdAt
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={20} maxSize={50}>
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();