ai-agent-inspector 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,379 @@
1
+ // API base URL
2
+ const API_BASE = '/v1';
3
+
4
+ // State
5
+ let currentRunId = null;
6
+ let currentEventId = null;
7
+ let runs = [];
8
+
9
+ // DOM elements
10
+ const runList = document.getElementById('runList');
11
+ const timeline = document.getElementById('timeline');
12
+ const detailView = document.getElementById('detailView');
13
+ const searchInput = document.getElementById('searchInput');
14
+ const statusFilter = document.getElementById('statusFilter');
15
+ const eventTypeFilter = document.getElementById('eventTypeFilter');
16
+ const themeToggle = document.getElementById('themeToggle');
17
+
18
+ // Load runs on page load
19
+ window.addEventListener('load', () => {
20
+ initTheme();
21
+ loadRuns();
22
+ });
23
+
24
+ // Search input debounce
25
+ let searchTimeout;
26
+ searchInput.addEventListener('input', (e) => {
27
+ clearTimeout(searchTimeout);
28
+ searchTimeout = setTimeout(() => filterRuns(), 300);
29
+ });
30
+
31
+ // Filter change handlers
32
+ statusFilter.addEventListener('change', filterRuns);
33
+ eventTypeFilter.addEventListener('change', () => {
34
+ if (currentRunId) {
35
+ loadTimeline(currentRunId);
36
+ }
37
+ });
38
+
39
+ async function loadRuns() {
40
+ try {
41
+ const response = await fetch(`${API_BASE}/runs?limit=100`);
42
+ const data = await response.json();
43
+ runs = data.runs || [];
44
+ renderRuns(runs);
45
+ } catch (error) {
46
+ runList.innerHTML = '<li class="error">Failed to load runs</li>';
47
+ console.error('Error loading runs:', error);
48
+ }
49
+ }
50
+
51
+ function renderRuns(runsToRender) {
52
+ if (runsToRender.length === 0) {
53
+ runList.innerHTML = '<li class="loading" style="padding: 40px;">No runs found</li>';
54
+ return;
55
+ }
56
+
57
+ runList.innerHTML = runsToRender.map(run => `
58
+ <li class="run-item ${currentRunId === run.id ? 'active' : ''}"
59
+ data-run-id="${run.id}"
60
+ onclick="selectRun('${run.id}')">
61
+ <div class="run-name">${escapeHtml(run.name || 'Unnamed Run')}</div>
62
+ <div class="run-meta">
63
+ ${formatTimestamp(run.started_at)}
64
+ <span class="run-status ${run.status}">${run.status}</span>
65
+ </div>
66
+ </li>
67
+ `).join('');
68
+ }
69
+
70
+ function filterRuns() {
71
+ const searchTerm = searchInput.value.toLowerCase();
72
+ const status = statusFilter.value;
73
+
74
+ const filtered = runs.filter(run => {
75
+ const matchesSearch = !searchTerm ||
76
+ (run.name && run.name.toLowerCase().includes(searchTerm));
77
+ const matchesStatus = !status || run.status === status;
78
+ return matchesSearch && matchesStatus;
79
+ });
80
+
81
+ renderRuns(filtered);
82
+ }
83
+
84
+ async function selectRun(runId) {
85
+ currentRunId = runId;
86
+
87
+ // Update UI
88
+ document.querySelectorAll('.run-item').forEach(item => {
89
+ item.classList.remove('active');
90
+ if (item.dataset.runId === runId) {
91
+ item.classList.add('active');
92
+ }
93
+ });
94
+
95
+ // Clear detail view
96
+ detailView.innerHTML = '<div class="detail-empty">Loading...</div>';
97
+
98
+ // Load timeline
99
+ await loadTimeline(runId);
100
+ }
101
+
102
+ async function loadTimeline(runId) {
103
+ try {
104
+ timeline.innerHTML = '<div class="loading">Loading timeline...</div>';
105
+
106
+ const response = await fetch(`${API_BASE}/runs/${runId}/timeline`);
107
+ const data = await response.json();
108
+ const events = data.events || [];
109
+
110
+ renderTimeline(events);
111
+ } catch (error) {
112
+ timeline.innerHTML = '<div class="error">Failed to load timeline</div>';
113
+ console.error('Error loading timeline:', error);
114
+ }
115
+ }
116
+
117
+ function renderTimeline(events) {
118
+ if (events.length === 0) {
119
+ timeline.innerHTML = '<div class="loading">No events in this run</div>';
120
+ return;
121
+ }
122
+
123
+ const eventTypeFilter = document.getElementById('eventTypeFilter').value;
124
+ const filteredEvents = eventTypeFilter
125
+ ? events.filter(e => e.type === eventTypeFilter)
126
+ : events;
127
+
128
+ if (filteredEvents.length === 0) {
129
+ timeline.innerHTML = '<div class="loading">No events match filter</div>';
130
+ return;
131
+ }
132
+
133
+ timeline.innerHTML = `
134
+ <div class="event-connector"></div>
135
+ ${filteredEvents.map(event => `
136
+ <div class="timeline-event"
137
+ onclick="showEventDetail('${event.id}')"
138
+ data-event-id="${event.id}">
139
+ <div class="event-icon ${event.type}">
140
+ ${getEventIcon(event.type)}
141
+ </div>
142
+ <div class="event-content">
143
+ <div class="event-type">${formatEventType(event.type)}</div>
144
+ <div class="event-timestamp">${formatTimestamp(event.timestamp)}</div>
145
+ <div class="event-summary">${getEventSummary(event)}</div>
146
+ </div>
147
+ </div>
148
+ `).join('')}
149
+ `;
150
+ }
151
+
152
+ async function showEventDetail(eventId) {
153
+ currentEventId = eventId;
154
+
155
+ try {
156
+ const response = await fetch(`${API_BASE}/runs/${currentRunId}/steps/${eventId}/data`);
157
+ const data = await response.json();
158
+
159
+ renderEventDetail(data.data);
160
+ } catch (error) {
161
+ detailView.innerHTML = '<div class="error">Failed to load event details</div>';
162
+ console.error('Error loading event details:', error);
163
+ }
164
+ }
165
+
166
+ function renderEventDetail(event) {
167
+ if (!event) {
168
+ detailView.innerHTML = '<div class="detail-empty">No event data</div>';
169
+ return;
170
+ }
171
+
172
+ const sections = [];
173
+ const richBlocks = [];
174
+
175
+ // Basic info
176
+ sections.push({
177
+ label: 'Event ID',
178
+ value: event.event_id || 'N/A'
179
+ });
180
+
181
+ sections.push({
182
+ label: 'Type',
183
+ value: formatEventType(event.type)
184
+ });
185
+
186
+ sections.push({
187
+ label: 'Timestamp',
188
+ value: formatTimestamp(event.timestamp_ms)
189
+ });
190
+
191
+ sections.push({
192
+ label: 'Status',
193
+ value: event.status || 'N/A'
194
+ });
195
+
196
+ if (event.duration_ms !== undefined) {
197
+ sections.push({
198
+ label: 'Duration',
199
+ value: `${event.duration_ms}ms`
200
+ });
201
+ }
202
+
203
+ // Type-specific details
204
+ if (event.type === 'llm_call') {
205
+ if (event.model) {
206
+ sections.push({ label: 'Model', value: event.model });
207
+ }
208
+ if (event.prompt) {
209
+ const parsed = parseMaybeJson(event.prompt);
210
+ if (parsed && Array.isArray(parsed)) {
211
+ richBlocks.push({
212
+ label: 'Prompt',
213
+ html: renderChatMessages(parsed)
214
+ });
215
+ } else {
216
+ sections.push({ label: 'Prompt', value: event.prompt });
217
+ }
218
+ }
219
+ if (event.response) {
220
+ sections.push({ label: 'Response', value: event.response });
221
+ }
222
+ if (event.total_tokens) {
223
+ sections.push({ label: 'Tokens', value: event.total_tokens.toString() });
224
+ }
225
+ } else if (event.type === 'tool_call') {
226
+ if (event.tool_name) {
227
+ sections.push({ label: 'Tool', value: event.tool_name });
228
+ }
229
+ if (event.tool_args) {
230
+ sections.push({
231
+ label: 'Arguments',
232
+ value: JSON.stringify(event.tool_args, null, 2)
233
+ });
234
+ }
235
+ if (event.tool_result) {
236
+ sections.push({
237
+ label: 'Result',
238
+ value: JSON.stringify(event.tool_result, null, 2)
239
+ });
240
+ }
241
+ } else if (event.type === 'memory_read' || event.type === 'memory_write') {
242
+ if (event.memory_key) {
243
+ sections.push({ label: 'Key', value: event.memory_key });
244
+ }
245
+ if (event.memory_value) {
246
+ sections.push({
247
+ label: 'Value',
248
+ value: JSON.stringify(event.memory_value, null, 2)
249
+ });
250
+ }
251
+ } else if (event.type === 'error') {
252
+ if (event.error_type) {
253
+ sections.push({ label: 'Error Type', value: event.error_type });
254
+ }
255
+ if (event.error_message) {
256
+ sections.push({ label: 'Message', value: event.error_message });
257
+ }
258
+ } else if (event.type === 'final_answer') {
259
+ if (event.answer) {
260
+ sections.push({ label: 'Answer', value: event.answer });
261
+ }
262
+ }
263
+
264
+ // Metadata
265
+ if (event.metadata && Object.keys(event.metadata).length > 0) {
266
+ sections.push({
267
+ label: 'Metadata',
268
+ value: JSON.stringify(event.metadata, null, 2)
269
+ });
270
+ }
271
+
272
+ // Render sections
273
+ const sectionHtml = sections.map(section => `
274
+ <div class="detail-section">
275
+ <div class="detail-label">${escapeHtml(section.label)}</div>
276
+ <div class="detail-value">${escapeHtml(section.value)}</div>
277
+ </div>
278
+ `).join('');
279
+
280
+ const richHtml = richBlocks.map(block => `
281
+ <div class="detail-section">
282
+ <div class="detail-label">${escapeHtml(block.label)}</div>
283
+ <div class="detail-chat">${block.html}</div>
284
+ </div>
285
+ `).join('');
286
+
287
+ detailView.innerHTML = richHtml + sectionHtml;
288
+ }
289
+
290
+ function getEventIcon(type) {
291
+ const icons = {
292
+ 'llm_call': '🤖',
293
+ 'tool_call': '🔧',
294
+ 'memory_read': '📖',
295
+ 'memory_write': '✍️',
296
+ 'error': '❌',
297
+ 'final_answer': '✅',
298
+ 'run_start': '▶️'
299
+ };
300
+ return icons[type] || '📌';
301
+ }
302
+
303
+ function formatEventType(type) {
304
+ return type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
305
+ }
306
+
307
+ function getEventSummary(event) {
308
+ switch (event.type) {
309
+ case 'llm_call':
310
+ return event.model ? `Model: ${event.model}` : 'LLM Call';
311
+ case 'tool_call':
312
+ return event.tool_name ? `Tool: ${event.tool_name}` : 'Tool Call';
313
+ case 'memory_read':
314
+ return event.memory_key ? `Read: ${event.memory_key}` : 'Memory Read';
315
+ case 'memory_write':
316
+ return event.memory_key ? `Write: ${event.memory_key}` : 'Memory Write';
317
+ case 'error':
318
+ return event.error_message || 'Error occurred';
319
+ case 'final_answer':
320
+ return event.answer ? event.answer.substring(0, 50) + '...' : 'Final answer';
321
+ default:
322
+ return event.name || event.type;
323
+ }
324
+ }
325
+
326
+ function formatTimestamp(ms) {
327
+ const date = new Date(ms);
328
+ return date.toLocaleString();
329
+ }
330
+
331
+ function escapeHtml(text) {
332
+ const div = document.createElement('div');
333
+ div.textContent = text;
334
+ return div.innerHTML;
335
+ }
336
+
337
+ function initTheme() {
338
+ const saved = localStorage.getItem('agent_inspector_theme') || 'auto';
339
+ applyTheme(saved);
340
+ themeToggle.addEventListener('click', cycleTheme);
341
+ }
342
+
343
+ function cycleTheme() {
344
+ const current = localStorage.getItem('agent_inspector_theme') || 'auto';
345
+ const next = current === 'auto' ? 'light' : current === 'light' ? 'dark' : 'auto';
346
+ applyTheme(next);
347
+ }
348
+
349
+ function applyTheme(theme) {
350
+ const root = document.documentElement;
351
+ root.classList.remove('theme-light', 'theme-dark');
352
+ if (theme === 'light') {
353
+ root.classList.add('theme-light');
354
+ } else if (theme === 'dark') {
355
+ root.classList.add('theme-dark');
356
+ }
357
+ localStorage.setItem('agent_inspector_theme', theme);
358
+ const label = theme === 'auto'
359
+ ? 'Theme: Auto'
360
+ : `Theme: ${theme[0].toUpperCase()}${theme.slice(1)}`;
361
+ themeToggle.textContent = label;
362
+ }
363
+
364
+ function parseMaybeJson(text) {
365
+ try {
366
+ return JSON.parse(text);
367
+ } catch (e) {
368
+ return null;
369
+ }
370
+ }
371
+
372
+ function renderChatMessages(messages) {
373
+ return messages.map(msg => `
374
+ <div class="chat-row ${escapeHtml(msg.role || 'unknown')}">
375
+ <div class="chat-role">${escapeHtml(msg.role || 'unknown')}</div>
376
+ <div class="chat-content">${escapeHtml(msg.content || '')}</div>
377
+ </div>
378
+ `).join('');
379
+ }