@lvce-editor/chat-debug-view 5.1.0 → 5.2.0

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.
@@ -1026,7 +1026,7 @@ const {
1026
1026
  const Response = 'response';
1027
1027
  const Preview = 'preview';
1028
1028
  const Timing = 'timing';
1029
- const detailTabs = [Response, Preview, Timing];
1029
+ const detailTabs = [Preview, Response, Timing];
1030
1030
  const isDetailTab = value => {
1031
1031
  return value === Response || value === Preview || value === Timing;
1032
1032
  };
@@ -1171,399 +1171,85 @@ const diff2 = uid => {
1171
1171
  return diff(oldState, newState);
1172
1172
  };
1173
1173
 
1174
- const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1175
- if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1176
- return startedEvent.toolName === finishedEvent.toolName;
1177
- }
1178
- return true;
1174
+ const handleDetailsContextMenu = state => {
1175
+ return state;
1179
1176
  };
1180
1177
 
1181
- const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1182
- return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$1(startedEvent, finishedEvent);
1183
- };
1178
+ // cspell:ignore IDBP
1184
1179
 
1185
1180
  const startedEventType$1 = 'tool-execution-started';
1186
1181
  const finishedEventType$1 = 'tool-execution-finished';
1187
- const mergedEventType = 'tool-execution';
1188
-
1189
- const isToolExecutionFinishedEvent = event => {
1190
- return event.type === finishedEventType$1;
1191
- };
1192
-
1193
- const isToolExecutionStartedEvent = event => {
1194
- return event.type === startedEventType$1;
1182
+ const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId) => {
1183
+ if (eventId < 1) {
1184
+ return undefined;
1185
+ }
1186
+ if (store.indexNames.contains(sessionIdIndexName)) {
1187
+ const index = store.index(sessionIdIndexName);
1188
+ const keys = await index.getAllKeys(sessionId, eventId);
1189
+ if (keys.length < eventId) {
1190
+ return undefined;
1191
+ }
1192
+ const key = keys.at(-1);
1193
+ if (key === undefined) {
1194
+ return undefined;
1195
+ }
1196
+ const event = await store.get(key);
1197
+ return event;
1198
+ }
1199
+ const all = await store.getAll();
1200
+ const events = all.filter(event => event.sessionId === sessionId);
1201
+ return events[eventId - 1];
1195
1202
  };
1196
-
1197
1203
  const getTimestamp$1 = value => {
1198
1204
  return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1199
1205
  };
1200
-
1201
- const getEndedTimestamp = event => {
1202
- return getTimestamp$1(event.ended) ?? getTimestamp$1(event.endTime) ?? getTimestamp$1(event.endTimestamp) ?? getTimestamp$1(event.timestamp);
1203
- };
1204
-
1205
- const eventStableIds = new WeakMap();
1206
- const eventStableIdState = {
1207
- nextStableEventId: 1
1208
- };
1209
-
1210
- const getOrCreateStableEventId = event => {
1211
- const existingStableEventId = eventStableIds.get(event);
1212
- if (existingStableEventId) {
1213
- return existingStableEventId;
1206
+ const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1207
+ if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1208
+ return startedEvent.toolName === finishedEvent.toolName;
1214
1209
  }
1215
- const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1216
- eventStableIds.set(event, stableEventId);
1217
- return stableEventId;
1218
- };
1219
-
1220
- const getStartedTimestamp = event => {
1221
- return getTimestamp$1(event.started) ?? getTimestamp$1(event.startTime) ?? getTimestamp$1(event.startTimestamp) ?? getTimestamp$1(event.timestamp);
1222
- };
1223
-
1224
- const setStableEventId = (event, stableEventId) => {
1225
- eventStableIds.set(event, stableEventId);
1210
+ return true;
1226
1211
  };
1227
-
1228
- const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent) => {
1229
- const ended = getEndedTimestamp(finishedEvent);
1230
- const {
1231
- eventId
1232
- } = startedEvent;
1233
- const started = getStartedTimestamp(startedEvent);
1234
- const mergedEvent = {
1212
+ const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent, eventId) => {
1213
+ const ended = getTimestamp$1(finishedEvent.ended) ?? getTimestamp$1(finishedEvent.endTime) ?? getTimestamp$1(finishedEvent.timestamp);
1214
+ const started = getTimestamp$1(startedEvent.started) ?? getTimestamp$1(startedEvent.startTime) ?? getTimestamp$1(startedEvent.timestamp);
1215
+ return {
1235
1216
  ...startedEvent,
1236
1217
  ...finishedEvent,
1237
1218
  ...(ended === undefined ? {} : {
1238
1219
  ended
1239
1220
  }),
1240
- ...(eventId === undefined ? {} : {
1241
- eventId
1242
- }),
1221
+ eventId,
1243
1222
  ...(started === undefined ? {} : {
1244
1223
  started
1245
1224
  }),
1246
- type: mergedEventType
1225
+ type: 'tool-execution'
1247
1226
  };
1248
- const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
1249
- setStableEventId(mergedEvent, stableEventId);
1250
- return mergedEvent;
1251
- };
1252
-
1253
- const getStableEventId = event => {
1254
- return getOrCreateStableEventId(event);
1255
- };
1256
-
1257
- const collapseToolExecutionEvents = events => {
1258
- const collapsedEvents = [];
1259
- for (let i = 0; i < events.length; i++) {
1260
- const event = events[i];
1261
- if (isToolExecutionStartedEvent(event)) {
1262
- const nextEvent = events[i + 1];
1263
- if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
1264
- collapsedEvents.push(mergeToolExecutionEvents$1(event, nextEvent));
1265
- i++;
1266
- continue;
1267
- }
1268
- }
1269
- collapsedEvents.push(event);
1270
- }
1271
- return collapsedEvents;
1272
- };
1273
-
1274
- const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1275
- return events.filter(event => {
1276
- if (!showInputEvents && event.type === 'handle-input') {
1277
- return false;
1278
- }
1279
- if (!showResponsePartEvents && event.type === 'sse-response-part') {
1280
- return false;
1281
- }
1282
- if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1283
- return false;
1284
- }
1285
- // hide session creation events by default — not useful in the debug view
1286
- if (event.type === 'chat-session-created') {
1287
- return false;
1288
- }
1289
- return true;
1290
- });
1291
- };
1292
-
1293
- const isNetworkEvent = event => {
1294
- const normalizedType = event.type.toLowerCase();
1295
- return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1296
- };
1297
-
1298
- const isStreamEvent = event => {
1299
- return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1300
1227
  };
1301
-
1302
- const toolEventTypePrefix = 'tool-execution';
1303
- const isToolEvent = event => {
1304
- return event.type.startsWith(toolEventTypePrefix);
1305
- };
1306
-
1307
- const isUiEvent = event => {
1308
- return event.type.startsWith('handle-') && event.type !== 'handle-response';
1309
- };
1310
-
1311
- const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1312
- switch (eventCategoryFilter) {
1313
- case Network:
1314
- return isNetworkEvent(event);
1315
- case Stream:
1316
- return isStreamEvent(event);
1317
- case Tools:
1318
- return isToolEvent(event);
1319
- case Ui:
1320
- return isUiEvent(event);
1321
- default:
1322
- return true;
1323
- }
1324
- };
1325
-
1326
- const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1327
- const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1328
- const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
1329
- const parsedFilter = parseFilterValue(filterValue);
1330
- const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
1331
- const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1332
- const {
1333
- filterText
1334
- } = parsedFilter;
1335
- if (!filterText) {
1336
- return filteredByCategory;
1337
- }
1338
- return filteredByCategory.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
1339
- };
1340
-
1341
- const toTimeNumber = value => {
1342
- if (typeof value === 'number' && Number.isFinite(value)) {
1343
- return value;
1344
- }
1345
- if (typeof value === 'string') {
1346
- const timestamp = Date.parse(value);
1347
- if (!Number.isNaN(timestamp)) {
1348
- return timestamp;
1349
- }
1350
- }
1351
- return undefined;
1352
- };
1353
-
1354
- const getEventTime = event => {
1355
- return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
1356
- };
1357
-
1358
- const maxBarUnits = 8;
1359
- const parseTimelineSeconds = value => {
1360
- const trimmed = value.trim();
1361
- if (!trimmed) {
1362
- return undefined;
1363
- }
1364
- const parsed = Number.parseFloat(trimmed);
1365
- if (!Number.isFinite(parsed) || parsed < 0) {
1228
+ const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId, summaryType) => {
1229
+ const event = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId);
1230
+ if (!event) {
1366
1231
  return undefined;
1367
1232
  }
1368
- return parsed;
1369
- };
1370
- const roundSeconds = value => {
1371
- return Number(value.toFixed(3));
1372
- };
1373
- const getEventsWithTime = events => {
1374
- return events.flatMap(event => {
1375
- const time = getEventTime(event);
1376
- if (time === undefined) {
1377
- return [];
1378
- }
1379
- return [{
1380
- event,
1381
- time
1382
- }];
1383
- });
1384
- };
1385
- const getTimelineDurationSeconds = events => {
1386
- const eventsWithTime = getEventsWithTime(events);
1387
- if (eventsWithTime.length === 0) {
1388
- return 0;
1233
+ if (summaryType !== 'tool-execution') {
1234
+ return {
1235
+ ...event,
1236
+ eventId
1237
+ };
1389
1238
  }
1390
- const baseTime = eventsWithTime[0].time;
1391
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1392
- return roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1393
- };
1394
- const getSelectionPercent = (value, durationSeconds) => {
1395
- if (durationSeconds <= 0) {
1396
- return 0;
1239
+ if (event.type !== startedEventType$1) {
1240
+ return {
1241
+ ...event,
1242
+ eventId
1243
+ };
1397
1244
  }
1398
- return Number((value / durationSeconds * 100).toFixed(3));
1399
- };
1400
- const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1401
- const parsedStart = parseTimelineSeconds(startValue);
1402
- const parsedEnd = parseTimelineSeconds(endValue);
1403
- if (parsedStart === undefined && parsedEnd === undefined) {
1245
+ const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1246
+ if (!nextEvent || nextEvent.type !== finishedEventType$1 || nextEvent.sessionId !== sessionId || !hasMatchingToolName$1(event, nextEvent)) {
1404
1247
  return {
1405
- endSeconds: null,
1406
- hasSelection: false,
1407
- startSeconds: null
1248
+ ...event,
1249
+ eventId
1408
1250
  };
1409
1251
  }
1410
- const rawStart = parsedStart ?? 0;
1411
- const rawEnd = parsedEnd ?? durationSeconds;
1412
- const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
1413
- const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
1414
- return {
1415
- endSeconds: roundSeconds(normalizedEnd),
1416
- hasSelection: true,
1417
- startSeconds: roundSeconds(normalizedStart)
1418
- };
1419
- };
1420
- const filterEventsByTimelineRange = (events, startValue, endValue) => {
1421
- const eventsWithTime = getEventsWithTime(events);
1422
- if (eventsWithTime.length === 0) {
1423
- return events;
1424
- }
1425
- const baseTime = eventsWithTime[0].time;
1426
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1427
- const durationSeconds = roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1428
- const range = getNormalizedRange(durationSeconds, startValue, endValue);
1429
- if (!range.hasSelection || range.startSeconds === null || range.endSeconds === null) {
1430
- return events;
1431
- }
1432
- const startTime = baseTime + range.startSeconds * 1000;
1433
- const endTime = baseTime + range.endSeconds * 1000;
1434
- return eventsWithTime.filter(item => item.time >= startTime && item.time <= endTime).map(item => item.event);
1435
- };
1436
- const getTimelineInfo = (events, startValue, endValue) => {
1437
- const eventsWithTime = getEventsWithTime(events);
1438
- if (eventsWithTime.length === 0) {
1439
- return {
1440
- buckets: [],
1441
- durationSeconds: 0,
1442
- endSeconds: null,
1443
- hasSelection: false,
1444
- selectionEndPercent: null,
1445
- selectionStartPercent: null,
1446
- startSeconds: null
1447
- };
1448
- }
1449
- const baseTime = eventsWithTime[0].time;
1450
- const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1451
- const durationMs = Math.max(0, lastTime - baseTime);
1452
- const durationSeconds = roundSeconds(durationMs / 1000);
1453
- const range = getNormalizedRange(durationSeconds, startValue, endValue);
1454
- const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
1455
- const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
1456
- const counts = Array.from({
1457
- length: bucketCount
1458
- }).fill(0);
1459
- for (const item of eventsWithTime) {
1460
- const offsetMs = item.time - baseTime;
1461
- const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
1462
- counts[index] += 1;
1463
- }
1464
- const maxCount = Math.max(...counts);
1465
- const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
1466
- const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
1467
- const buckets = counts.map((count, index) => {
1468
- const bucketStartMs = index * bucketDurationMs;
1469
- const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
1470
- const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
1471
- const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
1472
- const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
1473
- return {
1474
- count,
1475
- endSeconds: roundSeconds(bucketEndMs / 1000),
1476
- isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
1477
- startSeconds: roundSeconds(bucketStartMs / 1000),
1478
- unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
1479
- };
1480
- });
1481
- return {
1482
- buckets,
1483
- durationSeconds,
1484
- endSeconds: range.endSeconds,
1485
- hasSelection: range.hasSelection,
1486
- selectionEndPercent,
1487
- selectionStartPercent,
1488
- startSeconds: range.startSeconds
1489
- };
1490
- };
1491
-
1492
- // cspell:ignore IDBP
1493
-
1494
- const startedEventType = 'tool-execution-started';
1495
- const finishedEventType = 'tool-execution-finished';
1496
- const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId) => {
1497
- if (eventId < 1) {
1498
- return undefined;
1499
- }
1500
- if (store.indexNames.contains(sessionIdIndexName)) {
1501
- const index = store.index(sessionIdIndexName);
1502
- const keys = await index.getAllKeys(sessionId, eventId);
1503
- if (keys.length < eventId) {
1504
- return undefined;
1505
- }
1506
- const key = keys.at(-1);
1507
- if (key === undefined) {
1508
- return undefined;
1509
- }
1510
- const event = await store.get(key);
1511
- return event;
1512
- }
1513
- const all = await store.getAll();
1514
- const events = all.filter(event => event.sessionId === sessionId);
1515
- return events[eventId - 1];
1516
- };
1517
- const getTimestamp = value => {
1518
- return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1519
- };
1520
- const hasMatchingToolName = (startedEvent, finishedEvent) => {
1521
- if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1522
- return startedEvent.toolName === finishedEvent.toolName;
1523
- }
1524
- return true;
1525
- };
1526
- const mergeToolExecutionEvents = (startedEvent, finishedEvent, eventId) => {
1527
- const ended = getTimestamp(finishedEvent.ended) ?? getTimestamp(finishedEvent.endTime) ?? getTimestamp(finishedEvent.timestamp);
1528
- const started = getTimestamp(startedEvent.started) ?? getTimestamp(startedEvent.startTime) ?? getTimestamp(startedEvent.timestamp);
1529
- return {
1530
- ...startedEvent,
1531
- ...finishedEvent,
1532
- ...(ended === undefined ? {} : {
1533
- ended
1534
- }),
1535
- eventId,
1536
- ...(started === undefined ? {} : {
1537
- started
1538
- }),
1539
- type: 'tool-execution'
1540
- };
1541
- };
1542
- const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId, summaryType) => {
1543
- const event = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId);
1544
- if (!event) {
1545
- return undefined;
1546
- }
1547
- if (summaryType !== 'tool-execution') {
1548
- return {
1549
- ...event,
1550
- eventId
1551
- };
1552
- }
1553
- if (event.type !== startedEventType) {
1554
- return {
1555
- ...event,
1556
- eventId
1557
- };
1558
- }
1559
- const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1560
- if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
1561
- return {
1562
- ...event,
1563
- eventId
1564
- };
1565
- }
1566
- return mergeToolExecutionEvents(event, nextEvent, eventId);
1252
+ return mergeToolExecutionEvents$1(event, nextEvent, eventId);
1567
1253
  };
1568
1254
 
1569
1255
  const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
@@ -1787,84 +1473,388 @@ const cursorIteratorTraps = {
1787
1473
  return cachedFunc;
1788
1474
  }
1789
1475
  };
1790
- async function* iterate(...args) {
1791
- // tslint:disable-next-line:no-this-assignment
1792
- let cursor = this;
1793
- if (!(cursor instanceof IDBCursor)) {
1794
- cursor = await cursor.openCursor(...args);
1795
- }
1796
- if (!cursor) return;
1797
- cursor = cursor;
1798
- const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
1799
- ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
1800
- // Map this double-proxy back to the original, so other cursor methods work.
1801
- reverseTransformCache.set(proxiedCursor, unwrap(cursor));
1802
- while (cursor) {
1803
- yield proxiedCursor;
1804
- // If one of the advancing methods was not called, call continue().
1805
- cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
1806
- advanceResults.delete(proxiedCursor);
1476
+ async function* iterate(...args) {
1477
+ // tslint:disable-next-line:no-this-assignment
1478
+ let cursor = this;
1479
+ if (!(cursor instanceof IDBCursor)) {
1480
+ cursor = await cursor.openCursor(...args);
1481
+ }
1482
+ if (!cursor) return;
1483
+ cursor = cursor;
1484
+ const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
1485
+ ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
1486
+ // Map this double-proxy back to the original, so other cursor methods work.
1487
+ reverseTransformCache.set(proxiedCursor, unwrap(cursor));
1488
+ while (cursor) {
1489
+ yield proxiedCursor;
1490
+ // If one of the advancing methods was not called, call continue().
1491
+ cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
1492
+ advanceResults.delete(proxiedCursor);
1493
+ }
1494
+ }
1495
+ function isIteratorProp(target, prop) {
1496
+ return prop === Symbol.asyncIterator && instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor]) || prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore]);
1497
+ }
1498
+ replaceTraps(oldTraps => ({
1499
+ ...oldTraps,
1500
+ get(target, prop, receiver) {
1501
+ if (isIteratorProp(target, prop)) return iterate;
1502
+ return oldTraps.get(target, prop, receiver);
1503
+ },
1504
+ has(target, prop) {
1505
+ return isIteratorProp(target, prop) || oldTraps.has(target, prop);
1506
+ }
1507
+ }));
1508
+
1509
+ const openDatabaseDependencies = {
1510
+ openDB: openDB
1511
+ };
1512
+ const openDatabase = async (databaseName, dataBaseVersion) => {
1513
+ return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1514
+ };
1515
+
1516
+ const loadSelectedEventDependencies = {
1517
+ getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1518
+ openDatabase: openDatabase
1519
+ };
1520
+ const loadSelectedEvent = async (databaseName, dataBaseVersion, eventStoreName, sessionId, sessionIdIndexName, eventId, type) => {
1521
+ const database = await loadSelectedEventDependencies.openDatabase(databaseName, dataBaseVersion);
1522
+ try {
1523
+ if (!database.objectStoreNames.contains(eventStoreName)) {
1524
+ return null;
1525
+ }
1526
+ const transaction = database.transaction(eventStoreName, 'readonly');
1527
+ const store = transaction.objectStore(eventStoreName);
1528
+ const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1529
+ return event ?? null;
1530
+ } finally {
1531
+ database.close();
1532
+ }
1533
+ };
1534
+
1535
+ const hasMatchingToolName = (startedEvent, finishedEvent) => {
1536
+ if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1537
+ return startedEvent.toolName === finishedEvent.toolName;
1538
+ }
1539
+ return true;
1540
+ };
1541
+
1542
+ const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1543
+ return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName(startedEvent, finishedEvent);
1544
+ };
1545
+
1546
+ const startedEventType = 'tool-execution-started';
1547
+ const finishedEventType = 'tool-execution-finished';
1548
+ const mergedEventType = 'tool-execution';
1549
+
1550
+ const isToolExecutionFinishedEvent = event => {
1551
+ return event.type === finishedEventType;
1552
+ };
1553
+
1554
+ const isToolExecutionStartedEvent = event => {
1555
+ return event.type === startedEventType;
1556
+ };
1557
+
1558
+ const getTimestamp = value => {
1559
+ return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1560
+ };
1561
+
1562
+ const getEndedTimestamp = event => {
1563
+ return getTimestamp(event.ended) ?? getTimestamp(event.endTime) ?? getTimestamp(event.endTimestamp) ?? getTimestamp(event.timestamp);
1564
+ };
1565
+
1566
+ const eventStableIds = new WeakMap();
1567
+ const eventStableIdState = {
1568
+ nextStableEventId: 1
1569
+ };
1570
+
1571
+ const getOrCreateStableEventId = event => {
1572
+ const existingStableEventId = eventStableIds.get(event);
1573
+ if (existingStableEventId) {
1574
+ return existingStableEventId;
1575
+ }
1576
+ const stableEventId = `event-${eventStableIdState.nextStableEventId++}`;
1577
+ eventStableIds.set(event, stableEventId);
1578
+ return stableEventId;
1579
+ };
1580
+
1581
+ const getStartedTimestamp = event => {
1582
+ return getTimestamp(event.started) ?? getTimestamp(event.startTime) ?? getTimestamp(event.startTimestamp) ?? getTimestamp(event.timestamp);
1583
+ };
1584
+
1585
+ const setStableEventId = (event, stableEventId) => {
1586
+ eventStableIds.set(event, stableEventId);
1587
+ };
1588
+
1589
+ const mergeToolExecutionEvents = (startedEvent, finishedEvent) => {
1590
+ const ended = getEndedTimestamp(finishedEvent);
1591
+ const {
1592
+ eventId
1593
+ } = startedEvent;
1594
+ const started = getStartedTimestamp(startedEvent);
1595
+ const mergedEvent = {
1596
+ ...startedEvent,
1597
+ ...finishedEvent,
1598
+ ...(ended === undefined ? {} : {
1599
+ ended
1600
+ }),
1601
+ ...(eventId === undefined ? {} : {
1602
+ eventId
1603
+ }),
1604
+ ...(started === undefined ? {} : {
1605
+ started
1606
+ }),
1607
+ type: mergedEventType
1608
+ };
1609
+ const stableEventId = `${getOrCreateStableEventId(startedEvent)}:${getOrCreateStableEventId(finishedEvent)}`;
1610
+ setStableEventId(mergedEvent, stableEventId);
1611
+ return mergedEvent;
1612
+ };
1613
+
1614
+ const getStableEventId = event => {
1615
+ return getOrCreateStableEventId(event);
1616
+ };
1617
+
1618
+ const collapseToolExecutionEvents = events => {
1619
+ const collapsedEvents = [];
1620
+ for (let i = 0; i < events.length; i++) {
1621
+ const event = events[i];
1622
+ if (isToolExecutionStartedEvent(event)) {
1623
+ const nextEvent = events[i + 1];
1624
+ if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
1625
+ collapsedEvents.push(mergeToolExecutionEvents(event, nextEvent));
1626
+ i++;
1627
+ continue;
1628
+ }
1629
+ }
1630
+ collapsedEvents.push(event);
1631
+ }
1632
+ return collapsedEvents;
1633
+ };
1634
+
1635
+ const getVisibleEvents = (events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1636
+ return events.filter(event => {
1637
+ if (!showInputEvents && event.type === 'handle-input') {
1638
+ return false;
1639
+ }
1640
+ if (!showResponsePartEvents && event.type === 'sse-response-part') {
1641
+ return false;
1642
+ }
1643
+ if (!showEventStreamFinishedEvents && event.type === 'event-stream-finished') {
1644
+ return false;
1645
+ }
1646
+ // hide session creation events by default — not useful in the debug view
1647
+ if (event.type === 'chat-session-created') {
1648
+ return false;
1649
+ }
1650
+ return true;
1651
+ });
1652
+ };
1653
+
1654
+ const isNetworkEvent = event => {
1655
+ const normalizedType = event.type.toLowerCase();
1656
+ return normalizedType === 'request' || normalizedType === 'response' || normalizedType === 'handle-response' || normalizedType.includes('fetch') || normalizedType.includes('xhr');
1657
+ };
1658
+
1659
+ const isStreamEvent = event => {
1660
+ return event.type === 'sse-response-part' || event.type === 'event-stream-finished';
1661
+ };
1662
+
1663
+ const toolEventTypePrefix = 'tool-execution';
1664
+ const isToolEvent = event => {
1665
+ return event.type.startsWith(toolEventTypePrefix);
1666
+ };
1667
+
1668
+ const isUiEvent = event => {
1669
+ return event.type.startsWith('handle-') && event.type !== 'handle-response';
1670
+ };
1671
+
1672
+ const matchesEventCategoryFilter = (event, eventCategoryFilter) => {
1673
+ switch (eventCategoryFilter) {
1674
+ case Network:
1675
+ return isNetworkEvent(event);
1676
+ case Stream:
1677
+ return isStreamEvent(event);
1678
+ case Tools:
1679
+ return isToolEvent(event);
1680
+ case Ui:
1681
+ return isUiEvent(event);
1682
+ default:
1683
+ return true;
1684
+ }
1685
+ };
1686
+
1687
+ const getFilteredEvents = (events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents) => {
1688
+ const visibleEvents = getVisibleEvents(events, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
1689
+ const collapsedEvents = collapseToolExecutionEvents(visibleEvents);
1690
+ const parsedFilter = parseFilterValue(filterValue);
1691
+ const activeEventCategoryFilter = parsedFilter.eventCategoryFilter === All ? eventCategoryFilter : parsedFilter.eventCategoryFilter;
1692
+ const filteredByCategory = collapsedEvents.filter(event => matchesEventCategoryFilter(event, activeEventCategoryFilter));
1693
+ const {
1694
+ filterText
1695
+ } = parsedFilter;
1696
+ if (!filterText) {
1697
+ return filteredByCategory;
1698
+ }
1699
+ return filteredByCategory.filter(event => JSON.stringify(event).toLowerCase().includes(filterText));
1700
+ };
1701
+
1702
+ const toTimeNumber = value => {
1703
+ if (typeof value === 'number' && Number.isFinite(value)) {
1704
+ return value;
1705
+ }
1706
+ if (typeof value === 'string') {
1707
+ const timestamp = Date.parse(value);
1708
+ if (!Number.isNaN(timestamp)) {
1709
+ return timestamp;
1710
+ }
1711
+ }
1712
+ return undefined;
1713
+ };
1714
+
1715
+ const getEventTime = event => {
1716
+ return toTimeNumber(event.started ?? event.startTime ?? event.startTimestamp ?? event.timestamp);
1717
+ };
1718
+
1719
+ const maxBarUnits = 8;
1720
+ const parseTimelineSeconds = value => {
1721
+ const trimmed = value.trim();
1722
+ if (!trimmed) {
1723
+ return undefined;
1724
+ }
1725
+ const parsed = Number.parseFloat(trimmed);
1726
+ if (!Number.isFinite(parsed) || parsed < 0) {
1727
+ return undefined;
1728
+ }
1729
+ return parsed;
1730
+ };
1731
+ const roundSeconds = value => {
1732
+ return Number(value.toFixed(3));
1733
+ };
1734
+ const getEventsWithTime = events => {
1735
+ return events.flatMap(event => {
1736
+ const time = getEventTime(event);
1737
+ if (time === undefined) {
1738
+ return [];
1739
+ }
1740
+ return [{
1741
+ event,
1742
+ time
1743
+ }];
1744
+ });
1745
+ };
1746
+ const getTimelineDurationSeconds = events => {
1747
+ const eventsWithTime = getEventsWithTime(events);
1748
+ if (eventsWithTime.length === 0) {
1749
+ return 0;
1750
+ }
1751
+ const baseTime = eventsWithTime[0].time;
1752
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1753
+ return roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1754
+ };
1755
+ const getSelectionPercent = (value, durationSeconds) => {
1756
+ if (durationSeconds <= 0) {
1757
+ return 0;
1758
+ }
1759
+ return Number((value / durationSeconds * 100).toFixed(3));
1760
+ };
1761
+ const getNormalizedRange = (durationSeconds, startValue, endValue) => {
1762
+ const parsedStart = parseTimelineSeconds(startValue);
1763
+ const parsedEnd = parseTimelineSeconds(endValue);
1764
+ if (parsedStart === undefined && parsedEnd === undefined) {
1765
+ return {
1766
+ endSeconds: null,
1767
+ hasSelection: false,
1768
+ startSeconds: null
1769
+ };
1770
+ }
1771
+ const rawStart = parsedStart ?? 0;
1772
+ const rawEnd = parsedEnd ?? durationSeconds;
1773
+ const normalizedStart = Math.max(0, Math.min(durationSeconds, Math.min(rawStart, rawEnd)));
1774
+ const normalizedEnd = Math.max(0, Math.min(durationSeconds, Math.max(rawStart, rawEnd)));
1775
+ return {
1776
+ endSeconds: roundSeconds(normalizedEnd),
1777
+ hasSelection: true,
1778
+ startSeconds: roundSeconds(normalizedStart)
1779
+ };
1780
+ };
1781
+ const filterEventsByTimelineRange = (events, startValue, endValue) => {
1782
+ const eventsWithTime = getEventsWithTime(events);
1783
+ if (eventsWithTime.length === 0) {
1784
+ return events;
1807
1785
  }
1808
- }
1809
- function isIteratorProp(target, prop) {
1810
- return prop === Symbol.asyncIterator && instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor]) || prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore]);
1811
- }
1812
- replaceTraps(oldTraps => ({
1813
- ...oldTraps,
1814
- get(target, prop, receiver) {
1815
- if (isIteratorProp(target, prop)) return iterate;
1816
- return oldTraps.get(target, prop, receiver);
1817
- },
1818
- has(target, prop) {
1819
- return isIteratorProp(target, prop) || oldTraps.has(target, prop);
1786
+ const baseTime = eventsWithTime[0].time;
1787
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1788
+ const durationSeconds = roundSeconds(Math.max(0, lastTime - baseTime) / 1000);
1789
+ const range = getNormalizedRange(durationSeconds, startValue, endValue);
1790
+ if (!range.hasSelection || range.startSeconds === null || range.endSeconds === null) {
1791
+ return events;
1820
1792
  }
1821
- }));
1822
-
1823
- const openDatabaseDependencies = {
1824
- openDB: openDB
1825
- };
1826
- const openDatabase = async (databaseName, dataBaseVersion) => {
1827
- return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1828
- };
1829
-
1830
- const loadSelectedEventDependencies = {
1831
- getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1832
- openDatabase: openDatabase
1793
+ const startTime = baseTime + range.startSeconds * 1000;
1794
+ const endTime = baseTime + range.endSeconds * 1000;
1795
+ return eventsWithTime.filter(item => item.time >= startTime && item.time <= endTime).map(item => item.event);
1833
1796
  };
1834
- const loadSelectedEvent = async (databaseName, dataBaseVersion, eventStoreName, sessionId, sessionIdIndexName, eventId, type) => {
1835
- const database = await loadSelectedEventDependencies.openDatabase(databaseName, dataBaseVersion);
1836
- try {
1837
- if (!database.objectStoreNames.contains(eventStoreName)) {
1838
- return null;
1839
- }
1840
- const transaction = database.transaction(eventStoreName, 'readonly');
1841
- const store = transaction.objectStore(eventStoreName);
1842
- const event = await loadSelectedEventDependencies.getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
1843
- return event ?? null;
1844
- } finally {
1845
- database.close();
1797
+ const getTimelineInfo = (events, startValue, endValue) => {
1798
+ const eventsWithTime = getEventsWithTime(events);
1799
+ if (eventsWithTime.length === 0) {
1800
+ return {
1801
+ buckets: [],
1802
+ durationSeconds: 0,
1803
+ endSeconds: null,
1804
+ hasSelection: false,
1805
+ selectionEndPercent: null,
1806
+ selectionStartPercent: null,
1807
+ startSeconds: null
1808
+ };
1809
+ }
1810
+ const baseTime = eventsWithTime[0].time;
1811
+ const lastTime = eventsWithTime.at(-1)?.time ?? baseTime;
1812
+ const durationMs = Math.max(0, lastTime - baseTime);
1813
+ const durationSeconds = roundSeconds(durationMs / 1000);
1814
+ const range = getNormalizedRange(durationSeconds, startValue, endValue);
1815
+ const bucketCount = durationSeconds === 0 ? 1 : Math.max(12, Math.min(48, Math.ceil(durationSeconds)));
1816
+ const bucketDurationMs = durationMs === 0 ? 1000 : durationMs / bucketCount;
1817
+ const counts = Array.from({
1818
+ length: bucketCount
1819
+ }).fill(0);
1820
+ for (const item of eventsWithTime) {
1821
+ const offsetMs = item.time - baseTime;
1822
+ const index = durationMs === 0 ? 0 : Math.min(bucketCount - 1, Math.floor(offsetMs / durationMs * bucketCount));
1823
+ counts[index] += 1;
1846
1824
  }
1825
+ const maxCount = Math.max(...counts);
1826
+ const selectionStartPercent = range.hasSelection && range.startSeconds !== null ? getSelectionPercent(range.startSeconds, durationSeconds) : null;
1827
+ const selectionEndPercent = range.hasSelection && range.endSeconds !== null ? getSelectionPercent(range.endSeconds, durationSeconds) : null;
1828
+ const buckets = counts.map((count, index) => {
1829
+ const bucketStartMs = index * bucketDurationMs;
1830
+ const bucketEndMs = index === bucketCount - 1 ? durationMs : (index + 1) * bucketDurationMs;
1831
+ const hasSelection = range.hasSelection && range.startSeconds !== null && range.endSeconds !== null;
1832
+ const selectionStartMs = hasSelection ? range.startSeconds * 1000 : 0;
1833
+ const selectionEndMs = hasSelection ? range.endSeconds * 1000 : 0;
1834
+ return {
1835
+ count,
1836
+ endSeconds: roundSeconds(bucketEndMs / 1000),
1837
+ isSelected: hasSelection && bucketEndMs >= selectionStartMs && bucketStartMs <= selectionEndMs,
1838
+ startSeconds: roundSeconds(bucketStartMs / 1000),
1839
+ unitCount: count === 0 ? 0 : Math.max(1, Math.round(count / maxCount * maxBarUnits))
1840
+ };
1841
+ });
1842
+ return {
1843
+ buckets,
1844
+ durationSeconds,
1845
+ endSeconds: range.endSeconds,
1846
+ hasSelection: range.hasSelection,
1847
+ selectionEndPercent,
1848
+ selectionStartPercent,
1849
+ startSeconds: range.startSeconds
1850
+ };
1847
1851
  };
1848
1852
 
1849
- const handleEventRowClickDependencies = {
1850
- loadSelectedEvent: loadSelectedEvent
1851
- };
1852
1853
  const getCurrentEvents$2 = state => {
1853
1854
  const filteredEvents = getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
1854
1855
  return filterEventsByTimelineRange(filteredEvents, state.timelineStartSeconds, state.timelineEndSeconds);
1855
1856
  };
1856
- const parseSelectedEventIndex$1 = value => {
1857
- const parsed = Number.parseInt(value, 10);
1858
- if (Number.isNaN(parsed) || parsed < 0) {
1859
- return null;
1860
- }
1861
- return parsed;
1862
- };
1863
- const handleEventRowClick = async (state, value) => {
1864
- const selectedEventIndex = parseSelectedEventIndex$1(value);
1865
- if (selectedEventIndex === null) {
1866
- return state;
1867
- }
1857
+ const selectEventAtIndex = async (state, selectedEventIndex, dependencies) => {
1868
1858
  const currentEvents = getCurrentEvents$2(state);
1869
1859
  const selectedEvent = currentEvents[selectedEventIndex];
1870
1860
  if (!selectedEvent) {
@@ -1883,7 +1873,7 @@ const handleEventRowClick = async (state, value) => {
1883
1873
  selectedEventIndex
1884
1874
  };
1885
1875
  }
1886
- const selectedEventDetails = await handleEventRowClickDependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1876
+ const selectedEventDetails = await dependencies.loadSelectedEvent(state.databaseName, state.dataBaseVersion, state.eventStoreName, state.sessionId, state.sessionIdIndexName, selectedEvent.eventId, selectedEvent.type);
1887
1877
  return {
1888
1878
  ...state,
1889
1879
  selectedEvent: selectedEventDetails ?? selectedEvent,
@@ -1892,6 +1882,34 @@ const handleEventRowClick = async (state, value) => {
1892
1882
  };
1893
1883
  };
1894
1884
 
1885
+ const handleEventRowClickDependencies = {
1886
+ loadSelectedEvent: loadSelectedEvent
1887
+ };
1888
+ const isPrimaryButton = button => {
1889
+ return button === 0;
1890
+ };
1891
+ const parseSelectedEventIndex$1 = value => {
1892
+ const parsed = Number.parseInt(value, 10);
1893
+ if (Number.isNaN(parsed) || parsed < 0) {
1894
+ return null;
1895
+ }
1896
+ return parsed;
1897
+ };
1898
+ const handleEventRowClick = async (state, value, button) => {
1899
+ if (!isPrimaryButton(button)) {
1900
+ return state;
1901
+ }
1902
+ const selectedEventIndex = parseSelectedEventIndex$1(value);
1903
+ if (selectedEventIndex === null) {
1904
+ return state;
1905
+ }
1906
+ return selectEventAtIndex(state, selectedEventIndex, handleEventRowClickDependencies);
1907
+ };
1908
+
1909
+ const handleHeaderContextMenu = state => {
1910
+ return state;
1911
+ };
1912
+
1895
1913
  const getBoolean = value => {
1896
1914
  return value === true || value === 'true' || value === 'on' || value === '1';
1897
1915
  };
@@ -2114,7 +2132,15 @@ const handleTimelineDoubleClick = state => {
2114
2132
  };
2115
2133
 
2116
2134
  const getTimelineEvents = state => {
2117
- return getFilteredEvents(state.events, state.filterValue, state.eventCategoryFilter, state.showInputEvents, state.showResponsePartEvents, state.showEventStreamFinishedEvents);
2135
+ const {
2136
+ eventCategoryFilter,
2137
+ events,
2138
+ filterValue,
2139
+ showEventStreamFinishedEvents,
2140
+ showInputEvents,
2141
+ showResponsePartEvents
2142
+ } = state;
2143
+ return getFilteredEvents(events, filterValue, eventCategoryFilter, showInputEvents, showResponsePartEvents, showEventStreamFinishedEvents);
2118
2144
  };
2119
2145
 
2120
2146
  const getTimelineLeft = state => {
@@ -2910,6 +2936,21 @@ const getCss = state => {
2910
2936
  contain: content;
2911
2937
  }
2912
2938
 
2939
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(1) {
2940
+ flex: 1 1 140px;
2941
+ min-width: 0;
2942
+ }
2943
+
2944
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(2) {
2945
+ flex: 0 0 90px;
2946
+ justify-content: flex-end;
2947
+ }
2948
+
2949
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(3) {
2950
+ flex: 0 0 96px;
2951
+ justify-content: flex-end;
2952
+ }
2953
+
2913
2954
  .ChatDebugViewTableBody {
2914
2955
  display: flex;
2915
2956
  flex-direction: column;
@@ -2939,8 +2980,12 @@ const getCss = state => {
2939
2980
 
2940
2981
  .ChatDebugViewEventRow {
2941
2982
  padding: 2px 8px;
2942
- border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2943
- cursor: pointer;
2983
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 92%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 8%);
2984
+ cursor: default;
2985
+ }
2986
+
2987
+ .ChatDebugViewTableBody > .ChatDebugViewEventRow:nth-child(even) {
2988
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 84%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 16%);
2944
2989
  }
2945
2990
 
2946
2991
  .ChatDebugViewEventRow:hover {
@@ -2955,6 +3000,7 @@ const getCss = state => {
2955
3000
  text-overflow: ellipsis;
2956
3001
  white-space: nowrap;
2957
3002
  min-width: 0;
3003
+ pointer-events: none;
2958
3004
  contain: content;
2959
3005
  }
2960
3006
 
@@ -3584,14 +3630,87 @@ const diffTree = (oldNodes, newNodes) => {
3584
3630
  return removeTrailingNavigationPatches(patches);
3585
3631
  };
3586
3632
 
3633
+ const ChatDebugView = 'ChatDebugView';
3634
+ const ChatDebugViewDevtools = 'ChatDebugView--devtools';
3635
+ const ChatDebugViewDetails = 'ChatDebugViewDetails';
3636
+ const ChatDebugViewDetailsBody = 'ChatDebugViewDetailsBody';
3637
+ const ChatDebugViewDetailsClose = 'ChatDebugViewDetailsClose';
3638
+ const ChatDebugViewDetailsPanel = 'ChatDebugViewDetailsPanel';
3639
+ const ChatDebugViewDetailsTab = 'ChatDebugViewDetailsTab';
3640
+ const ChatDebugViewDetailsTabSelected = 'ChatDebugViewDetailsTabSelected';
3641
+ const ChatDebugViewDetailsTabs = 'ChatDebugViewDetailsTabs';
3642
+ const ChatDebugViewDetailsTop = 'ChatDebugViewDetailsTop';
3643
+ const ChatDebugViewDevtoolsMain = 'ChatDebugViewDevtoolsMain';
3644
+ const ChatDebugViewDevtoolsSplit = 'ChatDebugViewDevtoolsSplit';
3645
+ const ChatDebugViewEmpty = 'ChatDebugViewEmpty';
3646
+ const ChatDebugViewError = 'ChatDebugViewError';
3647
+ const ChatDebugViewEvent = 'ChatDebugViewEvent';
3648
+ const ChatDebugViewEventLineContent = 'ChatDebugViewEventLineContent';
3649
+ const ChatDebugViewEventLineNumber = 'ChatDebugViewEventLineNumber';
3650
+ const ChatDebugViewEventRow = 'ChatDebugViewEventRow';
3651
+ const ChatDebugViewEventRowSelected = 'ChatDebugViewEventRowSelected';
3652
+ const ChatDebugViewEvents = 'ChatDebugViewEvents';
3653
+ const ChatDebugViewEventsFullWidth = 'ChatDebugViewEventsFullWidth';
3654
+ const ChatDebugViewFilterInput = 'ChatDebugViewFilterInput';
3655
+ const ChatDebugViewFilterInputDevtools = 'ChatDebugViewFilterInput--devtools';
3656
+ const ChatDebugViewHeaderCell = 'ChatDebugViewHeaderCell';
3657
+ const ChatDebugViewQuickFilterInput = 'ChatDebugViewQuickFilterInput';
3658
+ const ChatDebugViewQuickFilterPill = 'ChatDebugViewQuickFilterPill';
3659
+ const ChatDebugViewQuickFilterPillSelected = 'ChatDebugViewQuickFilterPillSelected';
3660
+ const ChatDebugViewQuickFilters = 'ChatDebugViewQuickFilters';
3661
+ const ChatDebugViewSash = 'ChatDebugViewSash';
3662
+ const ChatDebugViewSashLine = 'ChatDebugViewSashLine';
3663
+ const ChatDebugViewTable = 'ChatDebugViewTable';
3664
+ const ChatDebugViewTableBody = 'ChatDebugViewTableBody';
3665
+ const ChatDebugViewTableHeader = 'ChatDebugViewTableHeader';
3666
+ const ChatDebugViewTableHeaderRow = 'ChatDebugViewTableHeaderRow';
3667
+ const ChatDebugViewTimeline = 'ChatDebugViewTimeline';
3668
+ const ChatDebugViewTimelineBucket = 'ChatDebugViewTimelineBucket';
3669
+ const ChatDebugViewTimelineBucketBar = 'ChatDebugViewTimelineBucketBar';
3670
+ const ChatDebugViewTimelineBucketBarSelected = 'ChatDebugViewTimelineBucketBarSelected';
3671
+ const ChatDebugViewTimelineBucketSelected = 'ChatDebugViewTimelineBucketSelected';
3672
+ const ChatDebugViewTimelineBucketUnit = 'ChatDebugViewTimelineBucketUnit';
3673
+ const ChatDebugViewTimelineBucketUnitEmpty = 'ChatDebugViewTimelineBucketUnitEmpty';
3674
+ const ChatDebugViewTimelineBuckets = 'ChatDebugViewTimelineBuckets';
3675
+ const ChatDebugViewTimelineInteractive = 'ChatDebugViewTimelineInteractive';
3676
+ const ChatDebugViewTimelinePresetInput = 'ChatDebugViewTimelinePresetInput';
3677
+ const ChatDebugViewTimelineSelectionMarker = 'ChatDebugViewTimelineSelectionMarker';
3678
+ const ChatDebugViewTimelineSelectionMarkerEnd = 'ChatDebugViewTimelineSelectionMarkerEnd';
3679
+ const ChatDebugViewTimelineSelectionMarkerStart = 'ChatDebugViewTimelineSelectionMarkerStart';
3680
+ const ChatDebugViewTimelineSelectionOverlay = 'ChatDebugViewTimelineSelectionOverlay';
3681
+ const ChatDebugViewTimelineSelectionRange = 'ChatDebugViewTimelineSelectionRange';
3682
+ const ChatDebugViewTimelineSummary = 'ChatDebugViewTimelineSummary';
3683
+ const ChatDebugViewTimelineTop = 'ChatDebugViewTimelineTop';
3684
+ const ChatDebugViewTiming = 'ChatDebugViewTiming';
3685
+ const ChatDebugViewTimingLabel = 'ChatDebugViewTimingLabel';
3686
+ const ChatDebugViewTimingRow = 'ChatDebugViewTimingRow';
3687
+ const ChatDebugViewTimingValue = 'ChatDebugViewTimingValue';
3688
+ const ChatDebugViewTop = 'ChatDebugViewTop';
3689
+ const ChatDebugViewTopDevtools = 'ChatDebugViewTop--devtools';
3690
+ const ChatDebugViewCell = 'ChatDebugViewCell';
3691
+ const ChatDebugViewCellDuration = 'ChatDebugViewCellDuration';
3692
+ const ChatDebugViewCellStatus = 'ChatDebugViewCellStatus';
3693
+ const ChatDebugViewCellStatusError = 'ChatDebugViewCellStatusError';
3694
+ const ChatDebugViewCellType = 'ChatDebugViewCellType';
3695
+ const InputBox = 'InputBox';
3696
+ const Row = 'row';
3697
+ const TokenBoolean = 'TokenBoolean';
3698
+ const TokenKey = 'TokenKey';
3699
+ const TokenNumeric = 'TokenNumeric';
3700
+ const TokenString = 'TokenString';
3701
+ const TokenText = 'TokenText';
3702
+ const joinClassNames = (...classNames) => {
3703
+ return classNames.filter(Boolean).join(' ');
3704
+ };
3705
+
3587
3706
  const getDebugErrorDom = errorMessage => {
3588
3707
  return [{
3589
3708
  childCount: 1,
3590
- className: 'ChatDebugView',
3709
+ className: ChatDebugView,
3591
3710
  type: Div
3592
3711
  }, {
3593
3712
  childCount: 1,
3594
- className: 'ChatDebugViewError',
3713
+ className: ChatDebugViewError,
3595
3714
  type: Div
3596
3715
  }, text(errorMessage)];
3597
3716
  };
@@ -3600,25 +3719,29 @@ const HandleInput = 4;
3600
3719
  const HandleFilterInput = 5;
3601
3720
  const HandleSimpleInput = 6;
3602
3721
  const HandleEventRowClick = 7;
3603
- const HandleSashPointerDown = 8;
3604
- const HandleSashPointerMove = 9;
3605
- const HandleSashPointerUp = 10;
3606
- const HandleTableBodyContextMenu = 11;
3607
- const HandleTimelinePointerDown = 12;
3608
- const HandleTimelinePointerMove = 13;
3609
- const HandleTimelinePointerUp = 14;
3610
- const HandleTimelineDoubleClick = 15;
3722
+ const HandleHeaderContextMenu = 8;
3723
+ const HandleSashPointerDown = 9;
3724
+ const HandleSashPointerMove = 10;
3725
+ const HandleSashPointerUp = 11;
3726
+ const HandleTableBodyContextMenu = 12;
3727
+ const HandleDetailsContextMenu = 13;
3728
+ const HandleTimelinePointerDown = 14;
3729
+ const HandleTimelinePointerMove = 15;
3730
+ const HandleTimelinePointerUp = 16;
3731
+ const HandleTimelineDoubleClick = 17;
3732
+ const HandleTableKeyDown = 18;
3611
3733
 
3612
3734
  const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) => {
3613
3735
  if (useDevtoolsLayout) {
3614
3736
  return [{
3615
3737
  childCount: 1 + (quickFilterNodes.length > 0 ? 1 : 0),
3616
- className: 'ChatDebugViewTop ChatDebugViewTop--devtools',
3738
+ className: joinClassNames(ChatDebugViewTop, ChatDebugViewTopDevtools),
3739
+ onContextMenu: HandleHeaderContextMenu,
3617
3740
  type: Search
3618
3741
  }, {
3619
3742
  autocomplete: 'off',
3620
3743
  childCount: 0,
3621
- className: 'InputBox ChatDebugViewFilterInput ChatDebugViewFilterInput--devtools',
3744
+ className: joinClassNames(InputBox, ChatDebugViewFilterInput, ChatDebugViewFilterInputDevtools),
3622
3745
  inputType: 'search',
3623
3746
  name: Filter,
3624
3747
  onInput: HandleFilterInput,
@@ -3629,12 +3752,13 @@ const getDebugViewTopDom = (filterValue, useDevtoolsLayout, quickFilterNodes) =>
3629
3752
  }
3630
3753
  return [{
3631
3754
  childCount: 1,
3632
- className: 'ChatDebugViewTop',
3755
+ className: ChatDebugViewTop,
3756
+ onContextMenu: HandleHeaderContextMenu,
3633
3757
  type: Search
3634
3758
  }, {
3635
3759
  autocomplete: 'off',
3636
3760
  childCount: 0,
3637
- className: 'InputBox ChatDebugViewFilterInput',
3761
+ className: joinClassNames(InputBox, ChatDebugViewFilterInput),
3638
3762
  inputType: 'search',
3639
3763
  name: Filter,
3640
3764
  onInput: HandleFilterInput,
@@ -3697,22 +3821,22 @@ const getStartText = event => {
3697
3821
  const getTimingRowDom = (label, value) => {
3698
3822
  return [{
3699
3823
  childCount: 2,
3700
- className: 'ChatDebugViewTimingRow',
3824
+ className: ChatDebugViewTimingRow,
3701
3825
  type: Div
3702
3826
  }, {
3703
3827
  childCount: 1,
3704
- className: 'ChatDebugViewTimingLabel',
3828
+ className: ChatDebugViewTimingLabel,
3705
3829
  type: Span
3706
3830
  }, text(label), {
3707
3831
  childCount: 1,
3708
- className: 'ChatDebugViewTimingValue',
3832
+ className: ChatDebugViewTimingValue,
3709
3833
  type: Span
3710
3834
  }, text(value)];
3711
3835
  };
3712
3836
  const getTimingDetailsDom = event => {
3713
3837
  return [{
3714
3838
  childCount: 3,
3715
- className: 'ChatDebugViewTiming',
3839
+ className: ChatDebugViewTiming,
3716
3840
  type: Div
3717
3841
  }, ...getTimingRowDom('Started', getStartText(event)), ...getTimingRowDom('Ended', getEndText(event)), ...getTimingRowDom('Duration', getDurationText(event))];
3718
3842
  };
@@ -3730,7 +3854,7 @@ const getTabNodes = selectedDetailTab => {
3730
3854
  'aria-controls': getPanelId(detailTab),
3731
3855
  'aria-selected': isSelected,
3732
3856
  childCount: 1,
3733
- className: isSelected ? 'ChatDebugViewDetailsTab ChatDebugViewDetailsTabSelected' : 'ChatDebugViewDetailsTab',
3857
+ className: joinClassNames(ChatDebugViewDetailsTab, isSelected && ChatDebugViewDetailsTabSelected),
3734
3858
  id: getTabId(detailTab),
3735
3859
  name: DetailTab,
3736
3860
  onChange: HandleSimpleInput,
@@ -3742,23 +3866,23 @@ const getTabNodes = selectedDetailTab => {
3742
3866
  }, text(getDetailTabLabel(detailTab))];
3743
3867
  });
3744
3868
  };
3745
- const getDetailsDom = (selectedEventNodes, selectedEvent = null, selectedDetailTab = Response) => {
3746
- if (selectedEventNodes.length === 0) {
3869
+ const getDetailsDom = (previewEventNodes, responseEventNodes = previewEventNodes, selectedEvent = null, selectedDetailTab = Response) => {
3870
+ if (previewEventNodes.length === 0 && responseEventNodes.length === 0) {
3747
3871
  return [];
3748
3872
  }
3749
- const contentNodes = selectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : selectedEventNodes;
3873
+ const contentNodes = selectedDetailTab === Timing && selectedEvent ? getTimingDetailsDom(selectedEvent) : selectedDetailTab === Preview ? previewEventNodes : responseEventNodes;
3750
3874
  return [{
3751
3875
  childCount: 2,
3752
- className: 'ChatDebugViewDetails',
3876
+ className: ChatDebugViewDetails,
3753
3877
  type: Div
3754
3878
  }, {
3755
3879
  childCount: 2,
3756
- className: 'ChatDebugViewDetailsTop',
3880
+ className: ChatDebugViewDetailsTop,
3757
3881
  type: Div
3758
3882
  }, {
3759
3883
  'aria-label': 'Close details',
3760
3884
  childCount: 0,
3761
- className: 'ChatDebugViewDetailsClose',
3885
+ className: ChatDebugViewDetailsClose,
3762
3886
  name: CloseDetails,
3763
3887
  onChange: HandleSimpleInput,
3764
3888
  onClick: HandleSimpleInput,
@@ -3767,19 +3891,20 @@ const getDetailsDom = (selectedEventNodes, selectedEvent = null, selectedDetailT
3767
3891
  }, {
3768
3892
  'aria-label': 'Detail sections',
3769
3893
  childCount: detailTabs.length,
3770
- className: 'ChatDebugViewDetailsTabs',
3894
+ className: ChatDebugViewDetailsTabs,
3771
3895
  role: 'tablist',
3772
3896
  type: Div
3773
3897
  }, ...getTabNodes(selectedDetailTab), {
3774
3898
  childCount: 1,
3775
- className: 'ChatDebugViewDetailsBody',
3899
+ className: ChatDebugViewDetailsBody,
3776
3900
  role: 'document',
3777
3901
  type: Div
3778
3902
  }, {
3779
3903
  'aria-labelledby': getTabId(selectedDetailTab),
3780
3904
  childCount: 1,
3781
- className: 'ChatDebugViewDetailsPanel',
3905
+ className: ChatDebugViewDetailsPanel,
3782
3906
  id: getPanelId(selectedDetailTab),
3907
+ onContextMenu: HandleDetailsContextMenu,
3783
3908
  role: 'tabpanel',
3784
3909
  type: Div
3785
3910
  }, ...contentNodes];
@@ -3818,6 +3943,19 @@ const getEventTypeLabel = event => {
3818
3943
  return `${event.type}, ${toolName}`;
3819
3944
  };
3820
3945
 
3946
+ const isRecord = value => {
3947
+ return typeof value === 'object' && value !== null;
3948
+ };
3949
+ const isErrorStatusCode = value => {
3950
+ if (typeof value === 'number') {
3951
+ return value >= 400;
3952
+ }
3953
+ if (typeof value === 'string') {
3954
+ const parsedStatus = Number(value);
3955
+ return Number.isFinite(parsedStatus) && parsedStatus >= 400;
3956
+ }
3957
+ return false;
3958
+ };
3821
3959
  const hasErrorStatus = event => {
3822
3960
  if (event.type === 'error') {
3823
3961
  return true;
@@ -3828,12 +3966,17 @@ const hasErrorStatus = event => {
3828
3966
  const {
3829
3967
  status
3830
3968
  } = event;
3831
- if (typeof status === 'number' && status >= 400) {
3969
+ if (isErrorStatusCode(status)) {
3832
3970
  return true;
3833
3971
  }
3834
- if (typeof status === 'string') {
3835
- const parsedStatus = Number(status);
3836
- if (Number.isFinite(parsedStatus) && parsedStatus >= 400) {
3972
+ const {
3973
+ result
3974
+ } = event;
3975
+ if (isRecord(result)) {
3976
+ if (isErrorStatusCode(result.status)) {
3977
+ return true;
3978
+ }
3979
+ if (typeof result.error === 'string' || typeof result.errorMessage === 'string' || typeof result.exception === 'string') {
3837
3980
  return true;
3838
3981
  }
3839
3982
  }
@@ -3851,23 +3994,20 @@ const getDevtoolsRows = (events, selectedEventIndex) => {
3851
3994
  const rowIndex = String(i);
3852
3995
  return [{
3853
3996
  childCount: 3,
3854
- className: `ChatDebugViewEventRow${isSelected ? ' ChatDebugViewEventRowSelected' : ''}`,
3997
+ className: joinClassNames(ChatDebugViewEventRow, isSelected && ChatDebugViewEventRowSelected),
3855
3998
  'data-index': rowIndex,
3856
3999
  type: Tr
3857
4000
  }, {
3858
4001
  childCount: 1,
3859
- className: 'ChatDebugViewCell ChatDebugViewCellType',
3860
- 'data-index': rowIndex,
4002
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellType),
3861
4003
  type: Td
3862
4004
  }, text(getEventTypeLabel(event)), {
3863
4005
  childCount: 1,
3864
- className: 'ChatDebugViewCell ChatDebugViewCellDuration',
3865
- 'data-index': rowIndex,
4006
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellDuration),
3866
4007
  type: Td
3867
4008
  }, text(getDurationText(event)), {
3868
4009
  childCount: 1,
3869
- className: `ChatDebugViewCell ChatDebugViewCellStatus${isErrorStatus ? ' ChatDebugViewCellStatusError' : ''}`,
3870
- 'data-index': rowIndex,
4010
+ className: joinClassNames(ChatDebugViewCell, ChatDebugViewCellStatus, isErrorStatus && ChatDebugViewCellStatusError),
3871
4011
  type: Td
3872
4012
  }, text(getStatusText(event))];
3873
4013
  });
@@ -3876,7 +4016,7 @@ const getDevtoolsRows = (events, selectedEventIndex) => {
3876
4016
  const getEmptyStateDom = emptyMessage => {
3877
4017
  return [{
3878
4018
  childCount: 1,
3879
- className: 'ChatDebugViewEmpty',
4019
+ className: ChatDebugViewEmpty,
3880
4020
  type: Div
3881
4021
  }, text(emptyMessage)];
3882
4022
  };
@@ -3926,32 +4066,32 @@ const getTokenSegments = json => {
3926
4066
  while (lookAheadIndex < json.length && whitespaceRegex.test(json[lookAheadIndex])) {
3927
4067
  lookAheadIndex++;
3928
4068
  }
3929
- const className = json[lookAheadIndex] === ':' ? 'TokenKey' : 'TokenString';
4069
+ const className = json[lookAheadIndex] === ':' ? TokenKey : TokenString;
3930
4070
  segments = pushToken(segments, className, tokenValue);
3931
4071
  continue;
3932
4072
  }
3933
4073
  const numberMatch = numberRegex.exec(json.slice(i));
3934
4074
  if (numberMatch) {
3935
- segments = pushToken(segments, 'TokenNumeric', numberMatch[0]);
4075
+ segments = pushToken(segments, TokenNumeric, numberMatch[0]);
3936
4076
  i += numberMatch[0].length;
3937
4077
  continue;
3938
4078
  }
3939
4079
  if (json.startsWith('true', i)) {
3940
- segments = pushToken(segments, 'TokenBoolean', 'true');
4080
+ segments = pushToken(segments, TokenBoolean, 'true');
3941
4081
  i += 4;
3942
4082
  continue;
3943
4083
  }
3944
4084
  if (json.startsWith('false', i)) {
3945
- segments = pushToken(segments, 'TokenBoolean', 'false');
4085
+ segments = pushToken(segments, TokenBoolean, 'false');
3946
4086
  i += 5;
3947
4087
  continue;
3948
4088
  }
3949
4089
  if (json.startsWith('null', i)) {
3950
- segments = pushToken(segments, 'TokenBoolean', 'null');
4090
+ segments = pushToken(segments, TokenBoolean, 'null');
3951
4091
  i += 4;
3952
4092
  continue;
3953
4093
  }
3954
- segments = pushToken(segments, 'TokenText', character);
4094
+ segments = pushToken(segments, TokenText, character);
3955
4095
  i++;
3956
4096
  }
3957
4097
  return segments;
@@ -3961,7 +4101,7 @@ const getJsonLines = value => {
3961
4101
  const json = JSON.stringify(value, null, 2);
3962
4102
  if (!json) {
3963
4103
  return [[{
3964
- className: 'TokenText',
4104
+ className: TokenText,
3965
4105
  value: String(json)
3966
4106
  }]];
3967
4107
  }
@@ -4001,50 +4141,93 @@ const getLineNodes = lines => {
4001
4141
  const lineContentNodes = getLineContentNodes(line);
4002
4142
  return [{
4003
4143
  childCount: 2,
4004
- className: 'row',
4144
+ className: Row,
4005
4145
  type: Div
4006
4146
  }, {
4007
4147
  childCount: 1,
4008
- className: 'ChatDebugViewEventLineNumber',
4148
+ className: ChatDebugViewEventLineNumber,
4009
4149
  type: Span
4010
4150
  }, text(String(index + 1)), {
4011
4151
  childCount: lineContentNodes.length / 2,
4012
- className: 'ChatDebugViewEventLineContent',
4152
+ className: ChatDebugViewEventLineContent,
4013
4153
  type: Span
4014
4154
  }, ...lineContentNodes];
4015
4155
  });
4016
4156
  };
4017
- const getEventNode = event => {
4018
- const renderedEvent = {
4019
- ...event,
4020
- type: getEventTypeLabel(event)
4021
- };
4022
- const lines = getJsonLines(renderedEvent);
4157
+ const isChatViewEvent = value => {
4158
+ return typeof value === 'object' && value !== null && typeof value.type === 'string';
4159
+ };
4160
+ const getEventNode = value => {
4161
+ const renderedValue = isChatViewEvent(value) ? {
4162
+ ...value,
4163
+ type: getEventTypeLabel(value)
4164
+ } : value;
4165
+ const lines = getJsonLines(renderedValue);
4023
4166
  const lineNodes = getLineNodes(lines);
4024
4167
  return [{
4025
4168
  childCount: lines.length,
4026
- className: 'ChatDebugViewEvent',
4169
+ className: ChatDebugViewEvent,
4027
4170
  type: Div
4028
4171
  }, ...lineNodes];
4029
4172
  };
4030
4173
 
4031
4174
  const getEventsClassName = hasSelectedEvent => {
4032
- const widthClassName = hasSelectedEvent ? 'ChatDebugViewEvents' : 'ChatDebugViewEvents ChatDebugViewEventsFullWidth';
4175
+ const widthClassName = joinClassNames(ChatDebugViewEvents, !hasSelectedEvent && ChatDebugViewEventsFullWidth);
4033
4176
  return widthClassName;
4034
4177
  };
4035
4178
 
4179
+ const hasOwn = (event, key) => {
4180
+ return Object.hasOwn(event, key);
4181
+ };
4182
+ const getPreviewName = event => {
4183
+ if (typeof event.name === 'string' && event.name) {
4184
+ return event.name;
4185
+ }
4186
+ if (typeof event.toolName === 'string' && event.toolName) {
4187
+ return event.toolName;
4188
+ }
4189
+ return undefined;
4190
+ };
4191
+ const shouldIncludeArguments = (event, name) => {
4192
+ if (!hasOwn(event, 'arguments')) {
4193
+ return false;
4194
+ }
4195
+ if (name === 'getWorkspaceUri') {
4196
+ return false;
4197
+ }
4198
+ return true;
4199
+ };
4200
+ const getPreviewEvent = event => {
4201
+ const name = getPreviewName(event);
4202
+ const previewEvent = {
4203
+ ...(name === undefined ? {} : {
4204
+ name
4205
+ }),
4206
+ ...(shouldIncludeArguments(event, name) ? {
4207
+ arguments: event.arguments
4208
+ } : {}),
4209
+ ...(hasOwn(event, 'result') ? {
4210
+ result: event.result
4211
+ } : {})
4212
+ };
4213
+ if (Object.keys(previewEvent).length > 0) {
4214
+ return previewEvent;
4215
+ }
4216
+ return event;
4217
+ };
4218
+
4036
4219
  const getSashNodesDom = hasSelectedEvent => {
4037
4220
  if (!hasSelectedEvent) {
4038
4221
  return [];
4039
4222
  }
4040
4223
  return [{
4041
4224
  childCount: 1,
4042
- className: 'ChatDebugViewSash',
4225
+ className: ChatDebugViewSash,
4043
4226
  onPointerDown: HandleSashPointerDown,
4044
4227
  type: Div
4045
4228
  }, {
4046
4229
  childCount: 0,
4047
- className: 'ChatDebugViewSashLine',
4230
+ className: ChatDebugViewSashLine,
4048
4231
  type: Div
4049
4232
  }];
4050
4233
  };
@@ -4052,7 +4235,7 @@ const getSashNodesDom = hasSelectedEvent => {
4052
4235
  const getTableBodyDom = (rowNodes, eventCount) => {
4053
4236
  return [{
4054
4237
  childCount: eventCount === 0 ? 1 : eventCount,
4055
- className: 'ChatDebugViewTableBody',
4238
+ className: ChatDebugViewTableBody,
4056
4239
  onContextMenu: HandleTableBodyContextMenu,
4057
4240
  onPointerDown: HandleEventRowClick,
4058
4241
  type: TBody
@@ -4062,25 +4245,25 @@ const getTableBodyDom = (rowNodes, eventCount) => {
4062
4245
  const getTableHeaderDom = () => {
4063
4246
  return [{
4064
4247
  childCount: 1,
4065
- className: 'ChatDebugViewTableHeader',
4248
+ className: ChatDebugViewTableHeader,
4066
4249
  type: THead
4067
4250
  }, {
4068
4251
  childCount: 3,
4069
- className: 'ChatDebugViewTableHeaderRow',
4252
+ className: ChatDebugViewTableHeaderRow,
4070
4253
  type: Tr
4071
4254
  }, {
4072
4255
  childCount: 1,
4073
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
4256
+ className: ChatDebugViewHeaderCell,
4074
4257
  scope: 'col',
4075
4258
  type: Th
4076
4259
  }, text('Type'), {
4077
4260
  childCount: 1,
4078
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
4261
+ className: ChatDebugViewHeaderCell,
4079
4262
  scope: 'col',
4080
4263
  type: Th
4081
4264
  }, text('Duration'), {
4082
4265
  childCount: 1,
4083
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
4266
+ className: ChatDebugViewHeaderCell,
4084
4267
  scope: 'col',
4085
4268
  type: Th
4086
4269
  }, text('Status')];
@@ -4089,16 +4272,20 @@ const getTableHeaderDom = () => {
4089
4272
  const getTableDom = (rowNodes, eventCount) => {
4090
4273
  return [{
4091
4274
  childCount: 2,
4092
- className: 'ChatDebugViewTable',
4275
+ className: ChatDebugViewTable,
4093
4276
  type: Table
4094
4277
  }, ...getTableHeaderDom(), ...getTableBodyDom(rowNodes, eventCount)];
4095
4278
  };
4096
4279
 
4280
+ const formatPercent = value => {
4281
+ return `${Number(value.toFixed(3))}%`;
4282
+ };
4283
+
4097
4284
  const getBucketUnitDom = unitCount => {
4098
4285
  if (unitCount === 0) {
4099
4286
  return [{
4100
4287
  childCount: 0,
4101
- className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
4288
+ className: joinClassNames(ChatDebugViewTimelineBucketUnit, ChatDebugViewTimelineBucketUnitEmpty),
4102
4289
  type: Div
4103
4290
  }];
4104
4291
  }
@@ -4106,7 +4293,7 @@ const getBucketUnitDom = unitCount => {
4106
4293
  length: unitCount
4107
4294
  }).fill({
4108
4295
  childCount: 0,
4109
- className: 'ChatDebugViewTimelineBucketUnit',
4296
+ className: ChatDebugViewTimelineBucketUnit,
4110
4297
  type: Div
4111
4298
  });
4112
4299
  };
@@ -4115,12 +4302,12 @@ const getBucketDom = bucket => {
4115
4302
  const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
4116
4303
  return [{
4117
4304
  childCount: 2,
4118
- className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
4305
+ className: joinClassNames(ChatDebugViewTimelineBucket, bucket.isSelected && ChatDebugViewTimelineBucketSelected),
4119
4306
  type: Label
4120
4307
  }, {
4121
4308
  checked: false,
4122
4309
  childCount: 0,
4123
- className: 'ChatDebugViewTimelinePresetInput',
4310
+ className: ChatDebugViewTimelinePresetInput,
4124
4311
  inputType: 'radio',
4125
4312
  name: TimelineRangePreset,
4126
4313
  onChange: HandleSimpleInput,
@@ -4128,11 +4315,24 @@ const getBucketDom = bucket => {
4128
4315
  value: presetValue
4129
4316
  }, {
4130
4317
  childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
4131
- className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
4318
+ className: joinClassNames(ChatDebugViewTimelineBucketBar, bucket.isSelected && ChatDebugViewTimelineBucketBarSelected),
4132
4319
  type: Div
4133
4320
  }, ...getBucketUnitDom(bucket.unitCount)];
4134
4321
  };
4135
4322
 
4323
+ const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4324
+ if (!timelineSelectionActive) {
4325
+ return {
4326
+ endSeconds: timelineEndSeconds,
4327
+ startSeconds: timelineStartSeconds
4328
+ };
4329
+ }
4330
+ return {
4331
+ endSeconds: timelineSelectionFocusSeconds,
4332
+ startSeconds: timelineSelectionAnchorSeconds
4333
+ };
4334
+ };
4335
+
4136
4336
  const formatTimelineSeconds = value => {
4137
4337
  if (Number.isInteger(value)) {
4138
4338
  return `${value}s`;
@@ -4148,21 +4348,6 @@ const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSec
4148
4348
  return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
4149
4349
  };
4150
4350
 
4151
- const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4152
- if (!timelineSelectionActive) {
4153
- return {
4154
- endSeconds: timelineEndSeconds,
4155
- startSeconds: timelineStartSeconds
4156
- };
4157
- }
4158
- return {
4159
- endSeconds: timelineSelectionFocusSeconds,
4160
- startSeconds: timelineSelectionAnchorSeconds
4161
- };
4162
- };
4163
- const formatPercent = value => {
4164
- return `${Number(value.toFixed(3))}%`;
4165
- };
4166
4351
  const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '') => {
4167
4352
  const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4168
4353
  const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
@@ -4171,45 +4356,45 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4171
4356
  }
4172
4357
  const selectionNodes = timelineInfo.hasSelection && timelineInfo.selectionStartPercent !== null && timelineInfo.selectionEndPercent !== null ? [{
4173
4358
  childCount: 0,
4174
- className: 'ChatDebugViewTimelineSelectionRange',
4359
+ className: ChatDebugViewTimelineSelectionRange,
4175
4360
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};width:${formatPercent(timelineInfo.selectionEndPercent - timelineInfo.selectionStartPercent)};`,
4176
4361
  type: Div
4177
4362
  }, {
4178
4363
  childCount: 0,
4179
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerStart',
4364
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerStart),
4180
4365
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};`,
4181
4366
  type: Div
4182
4367
  }, {
4183
4368
  childCount: 0,
4184
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerEnd',
4369
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerEnd),
4185
4370
  style: `left:${formatPercent(timelineInfo.selectionEndPercent)};`,
4186
4371
  type: Div
4187
4372
  }] : [];
4188
4373
  return [{
4189
4374
  childCount: 2,
4190
- className: 'ChatDebugViewTimeline',
4375
+ className: ChatDebugViewTimeline,
4191
4376
  type: Div
4192
4377
  }, {
4193
4378
  childCount: 1,
4194
- className: 'ChatDebugViewTimelineTop',
4379
+ className: ChatDebugViewTimelineTop,
4195
4380
  type: Div
4196
4381
  }, {
4197
4382
  childCount: 1,
4198
- className: 'ChatDebugViewTimelineSummary',
4383
+ className: ChatDebugViewTimelineSummary,
4199
4384
  type: Div
4200
4385
  }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
4201
4386
  childCount: 2,
4202
- className: 'ChatDebugViewTimelineInteractive',
4387
+ className: ChatDebugViewTimelineInteractive,
4203
4388
  onDoubleClick: HandleTimelineDoubleClick,
4204
4389
  onPointerDown: HandleTimelinePointerDown,
4205
4390
  type: Div
4206
4391
  }, {
4207
4392
  childCount: timelineInfo.buckets.length,
4208
- className: 'ChatDebugViewTimelineBuckets',
4393
+ className: ChatDebugViewTimelineBuckets,
4209
4394
  type: Div
4210
4395
  }, ...timelineInfo.buckets.flatMap(getBucketDom), {
4211
4396
  childCount: selectionNodes.length,
4212
- className: 'ChatDebugViewTimelineSelectionOverlay',
4397
+ className: ChatDebugViewTimelineSelectionOverlay,
4213
4398
  type: Div
4214
4399
  }, ...selectionNodes];
4215
4400
  };
@@ -4217,25 +4402,29 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4217
4402
  const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = 'No events have been found', timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response) => {
4218
4403
  const rowNodes = getDevtoolsRows(events, selectedEventIndex);
4219
4404
  const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4220
- const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4221
- const hasSelectedEvent = selectedEventNodes.length > 0;
4405
+ const previewEventNodes = selectedEvent ? getEventNode(getPreviewEvent(selectedEvent)) : [];
4406
+ const responseEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4407
+ const hasSelectedEvent = responseEventNodes.length > 0;
4222
4408
  const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length);
4223
4409
  const eventsClassName = getEventsClassName(hasSelectedEvent);
4224
- const detailsNodes = getDetailsDom(selectedEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4410
+ const detailsNodes = getDetailsDom(previewEventNodes, responseEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4225
4411
  const sashNodes = getSashNodesDom(hasSelectedEvent);
4226
4412
  const splitChildCount = hasSelectedEvent ? 3 : 1;
4227
4413
  const mainChildCount = 1 + (timelineNodes.length > 0 ? 1 : 0);
4228
4414
  return [{
4229
4415
  childCount: mainChildCount,
4230
- className: 'ChatDebugViewDevtoolsMain',
4416
+ className: ChatDebugViewDevtoolsMain,
4231
4417
  type: Div
4232
4418
  }, ...timelineNodes, {
4233
4419
  childCount: splitChildCount,
4234
- className: 'ChatDebugViewDevtoolsSplit',
4420
+ className: ChatDebugViewDevtoolsSplit,
4235
4421
  type: Div
4236
4422
  }, {
4237
4423
  childCount: 1,
4238
4424
  className: eventsClassName,
4425
+ onKeyDown: HandleTableKeyDown,
4426
+ role: 'application',
4427
+ tabIndex: 0,
4239
4428
  type: Div
4240
4429
  }, ...tableNodes, ...sashNodes, ...detailsNodes];
4241
4430
  };
@@ -4243,11 +4432,13 @@ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvent
4243
4432
  const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4244
4433
  return [{
4245
4434
  childCount: eventNodes.length === 0 ? 1 : eventNodes.length,
4246
- className: 'ChatDebugViewEvents',
4435
+ className: ChatDebugViewEvents,
4436
+ role: 'application',
4437
+ tabIndex: 0,
4247
4438
  type: Div
4248
4439
  }, ...(eventNodes.length === 0 ? [{
4249
4440
  childCount: 1,
4250
- className: errorMessage ? 'ChatDebugViewError' : 'ChatDebugViewEmpty',
4441
+ className: errorMessage ? ChatDebugViewError : ChatDebugViewEmpty,
4251
4442
  type: Div
4252
4443
  }, text(errorMessage || emptyMessage)] : eventNodes)];
4253
4444
  };
@@ -4255,18 +4446,18 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4255
4446
  const getQuickFilterNodes = (eventCategoryFilter, eventCategoryFilterOptions) => {
4256
4447
  return [{
4257
4448
  childCount: eventCategoryFilterOptions.length,
4258
- className: 'ChatDebugViewQuickFilters',
4449
+ className: ChatDebugViewQuickFilters,
4259
4450
  type: Div
4260
4451
  }, ...eventCategoryFilterOptions.flatMap(option => {
4261
4452
  const isSelected = option.value === eventCategoryFilter;
4262
4453
  return [{
4263
4454
  childCount: 2,
4264
- className: `ChatDebugViewQuickFilterPill${isSelected ? ' ChatDebugViewQuickFilterPillSelected' : ''}`,
4455
+ className: joinClassNames(ChatDebugViewQuickFilterPill, isSelected && ChatDebugViewQuickFilterPillSelected),
4265
4456
  type: Label
4266
4457
  }, {
4267
4458
  checked: isSelected,
4268
4459
  childCount: 0,
4269
- className: 'ChatDebugViewQuickFilterInput',
4460
+ className: ChatDebugViewQuickFilterInput,
4270
4461
  inputType: 'radio',
4271
4462
  name: EventCategoryFilter,
4272
4463
  onChange: HandleInput,
@@ -4318,7 +4509,7 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, eve
4318
4509
  const rootChildCount = 2;
4319
4510
  return [{
4320
4511
  childCount: rootChildCount,
4321
- className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
4512
+ className: joinClassNames(ChatDebugView, useDevtoolsLayout && ChatDebugViewDevtools),
4322
4513
  type: Div
4323
4514
  }, ...debugViewTopDom, ...contentNodes];
4324
4515
  };
@@ -4385,7 +4576,7 @@ const render2 = (uid, diffResult) => {
4385
4576
  const renderEventListeners = () => {
4386
4577
  return [{
4387
4578
  name: HandleEventRowClick,
4388
- params: ['handleEventRowClick', 'event.target.dataset.index']
4579
+ params: ['handleEventRowClick', 'event.target.dataset.index', 'event.button']
4389
4580
  }, {
4390
4581
  name: HandleTableBodyContextMenu,
4391
4582
  params: ['handleTableBodyContextMenu'],
@@ -4537,7 +4728,9 @@ const commandMap = {
4537
4728
  'ChatDebug.create': create,
4538
4729
  'ChatDebug.diff2': diff2,
4539
4730
  'ChatDebug.getCommandIds': getCommandIds,
4731
+ 'ChatDebug.handleDetailsContextMenu': wrapCommand(handleDetailsContextMenu),
4540
4732
  'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
4733
+ 'ChatDebug.handleHeaderContextMenu': wrapCommand(handleHeaderContextMenu),
4541
4734
  'ChatDebug.handleInput': wrapCommand(handleInput),
4542
4735
  'ChatDebug.handleSashPointerDown': wrapCommand(handleSashPointerDown),
4543
4736
  'ChatDebug.handleSashPointerMove': wrapCommand(handleSashPointerMove),