@lvce-editor/chat-debug-view 5.1.0 → 5.3.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,328 +1171,14 @@ 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;
1179
- };
1180
-
1181
- const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
1182
- return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$1(startedEvent, finishedEvent);
1183
- };
1184
-
1185
- const startedEventType$1 = 'tool-execution-started';
1186
- 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;
1195
- };
1196
-
1197
- const getTimestamp$1 = value => {
1198
- return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1199
- };
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;
1214
- }
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);
1226
- };
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 = {
1235
- ...startedEvent,
1236
- ...finishedEvent,
1237
- ...(ended === undefined ? {} : {
1238
- ended
1239
- }),
1240
- ...(eventId === undefined ? {} : {
1241
- eventId
1242
- }),
1243
- ...(started === undefined ? {} : {
1244
- started
1245
- }),
1246
- type: mergedEventType
1247
- };
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
- };
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) {
1366
- return undefined;
1367
- }
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;
1389
- }
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;
1397
- }
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) {
1404
- return {
1405
- endSeconds: null,
1406
- hasSelection: false,
1407
- startSeconds: null
1408
- };
1409
- }
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
- };
1174
+ const handleDetailsContextMenu = state => {
1175
+ return state;
1490
1176
  };
1491
1177
 
1492
1178
  // cspell:ignore IDBP
1493
1179
 
1494
- const startedEventType = 'tool-execution-started';
1495
- const finishedEventType = 'tool-execution-finished';
1180
+ const startedEventType$1 = 'tool-execution-started';
1181
+ const finishedEventType$1 = 'tool-execution-finished';
1496
1182
  const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId) => {
1497
1183
  if (eventId < 1) {
1498
1184
  return undefined;
@@ -1514,18 +1200,18 @@ const getRawEventBySessionIdAndEventId = async (store, sessionId, sessionIdIndex
1514
1200
  const events = all.filter(event => event.sessionId === sessionId);
1515
1201
  return events[eventId - 1];
1516
1202
  };
1517
- const getTimestamp = value => {
1203
+ const getTimestamp$1 = value => {
1518
1204
  return typeof value === 'string' || typeof value === 'number' ? value : undefined;
1519
1205
  };
1520
- const hasMatchingToolName = (startedEvent, finishedEvent) => {
1206
+ const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
1521
1207
  if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
1522
1208
  return startedEvent.toolName === finishedEvent.toolName;
1523
1209
  }
1524
1210
  return true;
1525
1211
  };
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);
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);
1529
1215
  return {
1530
1216
  ...startedEvent,
1531
1217
  ...finishedEvent,
@@ -1550,20 +1236,20 @@ const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdI
1550
1236
  eventId
1551
1237
  };
1552
1238
  }
1553
- if (event.type !== startedEventType) {
1239
+ if (event.type !== startedEventType$1) {
1554
1240
  return {
1555
1241
  ...event,
1556
1242
  eventId
1557
1243
  };
1558
1244
  }
1559
1245
  const nextEvent = await getRawEventBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId + 1);
1560
- if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
1246
+ if (!nextEvent || nextEvent.type !== finishedEventType$1 || nextEvent.sessionId !== sessionId || !hasMatchingToolName$1(event, nextEvent)) {
1561
1247
  return {
1562
1248
  ...event,
1563
1249
  eventId
1564
1250
  };
1565
1251
  }
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);
@@ -1826,45 +1512,349 @@ const openDatabaseDependencies = {
1826
1512
  const openDatabase = async (databaseName, dataBaseVersion) => {
1827
1513
  return openDatabaseDependencies.openDB(databaseName, dataBaseVersion);
1828
1514
  };
1829
-
1830
- const loadSelectedEventDependencies = {
1831
- getEventDetailsBySessionIdAndEventId: getEventDetailsBySessionIdAndEventId,
1832
- openDatabase: openDatabase
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;
1785
+ }
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;
1792
+ }
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 => {
@@ -2545,6 +2571,21 @@ const refresh = async state => {
2545
2571
  return refreshEvents(state);
2546
2572
  };
2547
2573
 
2574
+ const Button = 1;
2575
+ const Div = 4;
2576
+ const Input = 6;
2577
+ const Span = 8;
2578
+ const Table = 9;
2579
+ const TBody = 10;
2580
+ const Td = 11;
2581
+ const Text = 12;
2582
+ const Th = 13;
2583
+ const THead = 14;
2584
+ const Tr = 15;
2585
+ const Search = 42;
2586
+ const Label = 66;
2587
+ const Reference = 100;
2588
+
2548
2589
  const ClientX = 'event.clientX';
2549
2590
  const ClientY = 'event.clientY';
2550
2591
  const TargetChecked = 'event.target.checked';
@@ -2910,6 +2951,21 @@ const getCss = state => {
2910
2951
  contain: content;
2911
2952
  }
2912
2953
 
2954
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(1) {
2955
+ flex: 1 1 140px;
2956
+ min-width: 0;
2957
+ }
2958
+
2959
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(2) {
2960
+ flex: 0 0 90px;
2961
+ justify-content: flex-end;
2962
+ }
2963
+
2964
+ .ChatDebugViewTableHeaderRow > .ChatDebugViewHeaderCell:nth-child(3) {
2965
+ flex: 0 0 96px;
2966
+ justify-content: flex-end;
2967
+ }
2968
+
2913
2969
  .ChatDebugViewTableBody {
2914
2970
  display: flex;
2915
2971
  flex-direction: column;
@@ -2939,8 +2995,12 @@ const getCss = state => {
2939
2995
 
2940
2996
  .ChatDebugViewEventRow {
2941
2997
  padding: 2px 8px;
2942
- border-bottom: 1px solid var(--vscode-editorWidget-border, #454545);
2943
- cursor: pointer;
2998
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 92%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 8%);
2999
+ cursor: default;
3000
+ }
3001
+
3002
+ .ChatDebugViewTableBody > .ChatDebugViewEventRow:nth-child(even) {
3003
+ background: color-mix(in srgb, var(--vscode-editorWidget-background, transparent) 84%, var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31)) 16%);
2944
3004
  }
2945
3005
 
2946
3006
  .ChatDebugViewEventRow:hover {
@@ -2955,6 +3015,7 @@ const getCss = state => {
2955
3015
  text-overflow: ellipsis;
2956
3016
  white-space: nowrap;
2957
3017
  min-width: 0;
3018
+ pointer-events: none;
2958
3019
  contain: content;
2959
3020
  }
2960
3021
 
@@ -3273,21 +3334,6 @@ const renderCss = (oldState, newState) => {
3273
3334
  return [SetCss, newState.uid, css];
3274
3335
  };
3275
3336
 
3276
- const Button = 1;
3277
- const Div = 4;
3278
- const Input = 6;
3279
- const Span = 8;
3280
- const Table = 9;
3281
- const TBody = 10;
3282
- const Td = 11;
3283
- const Text = 12;
3284
- const Th = 13;
3285
- const THead = 14;
3286
- const Tr = 15;
3287
- const Search = 42;
3288
- const Label = 66;
3289
- const Reference = 100;
3290
-
3291
3337
  const text = data => {
3292
3338
  return {
3293
3339
  childCount: 0,
@@ -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,99 @@ 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 isChatMessageUpdatedEvent = event => {
4183
+ return event.type === 'chat-message-updated';
4184
+ };
4185
+ const getPreviewName = event => {
4186
+ if (typeof event.name === 'string' && event.name) {
4187
+ return event.name;
4188
+ }
4189
+ if (typeof event.toolName === 'string' && event.toolName) {
4190
+ return event.toolName;
4191
+ }
4192
+ return undefined;
4193
+ };
4194
+ const shouldIncludeArguments = (event, name) => {
4195
+ if (!hasOwn(event, 'arguments')) {
4196
+ return false;
4197
+ }
4198
+ if (name === 'getWorkspaceUri') {
4199
+ return false;
4200
+ }
4201
+ return true;
4202
+ };
4203
+ const getPreviewEvent = event => {
4204
+ if (isChatMessageUpdatedEvent(event) && typeof event.text === 'string') {
4205
+ return event.text;
4206
+ }
4207
+ const name = getPreviewName(event);
4208
+ const previewEvent = {
4209
+ ...(name === undefined ? {} : {
4210
+ name
4211
+ }),
4212
+ ...(shouldIncludeArguments(event, name) ? {
4213
+ arguments: event.arguments
4214
+ } : {}),
4215
+ ...(hasOwn(event, 'result') ? {
4216
+ result: event.result
4217
+ } : {})
4218
+ };
4219
+ if (Object.keys(previewEvent).length > 0) {
4220
+ return previewEvent;
4221
+ }
4222
+ return event;
4223
+ };
4224
+
4036
4225
  const getSashNodesDom = hasSelectedEvent => {
4037
4226
  if (!hasSelectedEvent) {
4038
4227
  return [];
4039
4228
  }
4040
4229
  return [{
4041
4230
  childCount: 1,
4042
- className: 'ChatDebugViewSash',
4231
+ className: ChatDebugViewSash,
4043
4232
  onPointerDown: HandleSashPointerDown,
4044
4233
  type: Div
4045
4234
  }, {
4046
4235
  childCount: 0,
4047
- className: 'ChatDebugViewSashLine',
4236
+ className: ChatDebugViewSashLine,
4048
4237
  type: Div
4049
4238
  }];
4050
4239
  };
@@ -4052,7 +4241,7 @@ const getSashNodesDom = hasSelectedEvent => {
4052
4241
  const getTableBodyDom = (rowNodes, eventCount) => {
4053
4242
  return [{
4054
4243
  childCount: eventCount === 0 ? 1 : eventCount,
4055
- className: 'ChatDebugViewTableBody',
4244
+ className: ChatDebugViewTableBody,
4056
4245
  onContextMenu: HandleTableBodyContextMenu,
4057
4246
  onPointerDown: HandleEventRowClick,
4058
4247
  type: TBody
@@ -4062,25 +4251,25 @@ const getTableBodyDom = (rowNodes, eventCount) => {
4062
4251
  const getTableHeaderDom = () => {
4063
4252
  return [{
4064
4253
  childCount: 1,
4065
- className: 'ChatDebugViewTableHeader',
4254
+ className: ChatDebugViewTableHeader,
4066
4255
  type: THead
4067
4256
  }, {
4068
4257
  childCount: 3,
4069
- className: 'ChatDebugViewTableHeaderRow',
4258
+ className: ChatDebugViewTableHeaderRow,
4070
4259
  type: Tr
4071
4260
  }, {
4072
4261
  childCount: 1,
4073
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellType',
4262
+ className: ChatDebugViewHeaderCell,
4074
4263
  scope: 'col',
4075
4264
  type: Th
4076
4265
  }, text('Type'), {
4077
4266
  childCount: 1,
4078
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellDuration',
4267
+ className: ChatDebugViewHeaderCell,
4079
4268
  scope: 'col',
4080
4269
  type: Th
4081
4270
  }, text('Duration'), {
4082
4271
  childCount: 1,
4083
- className: 'ChatDebugViewHeaderCell ChatDebugViewCellStatus',
4272
+ className: ChatDebugViewHeaderCell,
4084
4273
  scope: 'col',
4085
4274
  type: Th
4086
4275
  }, text('Status')];
@@ -4089,16 +4278,20 @@ const getTableHeaderDom = () => {
4089
4278
  const getTableDom = (rowNodes, eventCount) => {
4090
4279
  return [{
4091
4280
  childCount: 2,
4092
- className: 'ChatDebugViewTable',
4281
+ className: ChatDebugViewTable,
4093
4282
  type: Table
4094
4283
  }, ...getTableHeaderDom(), ...getTableBodyDom(rowNodes, eventCount)];
4095
4284
  };
4096
4285
 
4286
+ const formatPercent = value => {
4287
+ return `${Number(value.toFixed(3))}%`;
4288
+ };
4289
+
4097
4290
  const getBucketUnitDom = unitCount => {
4098
4291
  if (unitCount === 0) {
4099
4292
  return [{
4100
4293
  childCount: 0,
4101
- className: 'ChatDebugViewTimelineBucketUnit ChatDebugViewTimelineBucketUnitEmpty',
4294
+ className: joinClassNames(ChatDebugViewTimelineBucketUnit, ChatDebugViewTimelineBucketUnitEmpty),
4102
4295
  type: Div
4103
4296
  }];
4104
4297
  }
@@ -4106,7 +4299,7 @@ const getBucketUnitDom = unitCount => {
4106
4299
  length: unitCount
4107
4300
  }).fill({
4108
4301
  childCount: 0,
4109
- className: 'ChatDebugViewTimelineBucketUnit',
4302
+ className: ChatDebugViewTimelineBucketUnit,
4110
4303
  type: Div
4111
4304
  });
4112
4305
  };
@@ -4115,12 +4308,12 @@ const getBucketDom = bucket => {
4115
4308
  const presetValue = `${formatTimelinePresetValue(bucket.startSeconds)}:${formatTimelinePresetValue(bucket.endSeconds)}`;
4116
4309
  return [{
4117
4310
  childCount: 2,
4118
- className: `ChatDebugViewTimelineBucket${bucket.isSelected ? ' ChatDebugViewTimelineBucketSelected' : ''}`,
4311
+ className: joinClassNames(ChatDebugViewTimelineBucket, bucket.isSelected && ChatDebugViewTimelineBucketSelected),
4119
4312
  type: Label
4120
4313
  }, {
4121
4314
  checked: false,
4122
4315
  childCount: 0,
4123
- className: 'ChatDebugViewTimelinePresetInput',
4316
+ className: ChatDebugViewTimelinePresetInput,
4124
4317
  inputType: 'radio',
4125
4318
  name: TimelineRangePreset,
4126
4319
  onChange: HandleSimpleInput,
@@ -4128,11 +4321,24 @@ const getBucketDom = bucket => {
4128
4321
  value: presetValue
4129
4322
  }, {
4130
4323
  childCount: bucket.unitCount === 0 ? 1 : bucket.unitCount,
4131
- className: `ChatDebugViewTimelineBucketBar${bucket.isSelected ? ' ChatDebugViewTimelineBucketBarSelected' : ''}`,
4324
+ className: joinClassNames(ChatDebugViewTimelineBucketBar, bucket.isSelected && ChatDebugViewTimelineBucketBarSelected),
4132
4325
  type: Div
4133
4326
  }, ...getBucketUnitDom(bucket.unitCount)];
4134
4327
  };
4135
4328
 
4329
+ const getEffectiveTimelineRange = (timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds) => {
4330
+ if (!timelineSelectionActive) {
4331
+ return {
4332
+ endSeconds: timelineEndSeconds,
4333
+ startSeconds: timelineStartSeconds
4334
+ };
4335
+ }
4336
+ return {
4337
+ endSeconds: timelineSelectionFocusSeconds,
4338
+ startSeconds: timelineSelectionAnchorSeconds
4339
+ };
4340
+ };
4341
+
4136
4342
  const formatTimelineSeconds = value => {
4137
4343
  if (Number.isInteger(value)) {
4138
4344
  return `${value}s`;
@@ -4148,21 +4354,6 @@ const getTimelineSummary = (timelineEvents, timelineStartSeconds, timelineEndSec
4148
4354
  return `Window 0s-${formatTimelineSeconds(timelineInfo.durationSeconds)} of ${formatTimelineSeconds(timelineInfo.durationSeconds)}`;
4149
4355
  };
4150
4356
 
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
4357
  const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '') => {
4167
4358
  const effectiveRange = getEffectiveTimelineRange(timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4168
4359
  const timelineInfo = getTimelineInfo(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds);
@@ -4171,45 +4362,45 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4171
4362
  }
4172
4363
  const selectionNodes = timelineInfo.hasSelection && timelineInfo.selectionStartPercent !== null && timelineInfo.selectionEndPercent !== null ? [{
4173
4364
  childCount: 0,
4174
- className: 'ChatDebugViewTimelineSelectionRange',
4365
+ className: ChatDebugViewTimelineSelectionRange,
4175
4366
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};width:${formatPercent(timelineInfo.selectionEndPercent - timelineInfo.selectionStartPercent)};`,
4176
4367
  type: Div
4177
4368
  }, {
4178
4369
  childCount: 0,
4179
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerStart',
4370
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerStart),
4180
4371
  style: `left:${formatPercent(timelineInfo.selectionStartPercent)};`,
4181
4372
  type: Div
4182
4373
  }, {
4183
4374
  childCount: 0,
4184
- className: 'ChatDebugViewTimelineSelectionMarker ChatDebugViewTimelineSelectionMarkerEnd',
4375
+ className: joinClassNames(ChatDebugViewTimelineSelectionMarker, ChatDebugViewTimelineSelectionMarkerEnd),
4185
4376
  style: `left:${formatPercent(timelineInfo.selectionEndPercent)};`,
4186
4377
  type: Div
4187
4378
  }] : [];
4188
4379
  return [{
4189
4380
  childCount: 2,
4190
- className: 'ChatDebugViewTimeline',
4381
+ className: ChatDebugViewTimeline,
4191
4382
  type: Div
4192
4383
  }, {
4193
4384
  childCount: 1,
4194
- className: 'ChatDebugViewTimelineTop',
4385
+ className: ChatDebugViewTimelineTop,
4195
4386
  type: Div
4196
4387
  }, {
4197
4388
  childCount: 1,
4198
- className: 'ChatDebugViewTimelineSummary',
4389
+ className: ChatDebugViewTimelineSummary,
4199
4390
  type: Div
4200
4391
  }, text(getTimelineSummary(timelineEvents, effectiveRange.startSeconds, effectiveRange.endSeconds)), {
4201
4392
  childCount: 2,
4202
- className: 'ChatDebugViewTimelineInteractive',
4393
+ className: ChatDebugViewTimelineInteractive,
4203
4394
  onDoubleClick: HandleTimelineDoubleClick,
4204
4395
  onPointerDown: HandleTimelinePointerDown,
4205
4396
  type: Div
4206
4397
  }, {
4207
4398
  childCount: timelineInfo.buckets.length,
4208
- className: 'ChatDebugViewTimelineBuckets',
4399
+ className: ChatDebugViewTimelineBuckets,
4209
4400
  type: Div
4210
4401
  }, ...timelineInfo.buckets.flatMap(getBucketDom), {
4211
4402
  childCount: selectionNodes.length,
4212
- className: 'ChatDebugViewTimelineSelectionOverlay',
4403
+ className: ChatDebugViewTimelineSelectionOverlay,
4213
4404
  type: Div
4214
4405
  }, ...selectionNodes];
4215
4406
  };
@@ -4217,25 +4408,29 @@ const getTimelineNodes = (timelineEvents, timelineStartSeconds, timelineEndSecon
4217
4408
  const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage = 'No events have been found', timelineSelectionActive = false, timelineSelectionAnchorSeconds = '', timelineSelectionFocusSeconds = '', selectedDetailTab = Response) => {
4218
4409
  const rowNodes = getDevtoolsRows(events, selectedEventIndex);
4219
4410
  const timelineNodes = getTimelineNodes(timelineEvents, timelineStartSeconds, timelineEndSeconds, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds);
4220
- const selectedEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4221
- const hasSelectedEvent = selectedEventNodes.length > 0;
4411
+ const previewEventNodes = selectedEvent ? getEventNode(getPreviewEvent(selectedEvent)) : [];
4412
+ const responseEventNodes = selectedEvent ? getEventNode(selectedEvent) : [];
4413
+ const hasSelectedEvent = responseEventNodes.length > 0;
4222
4414
  const tableNodes = events.length === 0 ? getEmptyStateDom(emptyMessage) : getTableDom(rowNodes, events.length);
4223
4415
  const eventsClassName = getEventsClassName(hasSelectedEvent);
4224
- const detailsNodes = getDetailsDom(selectedEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4416
+ const detailsNodes = getDetailsDom(previewEventNodes, responseEventNodes, selectedEvent, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response);
4225
4417
  const sashNodes = getSashNodesDom(hasSelectedEvent);
4226
4418
  const splitChildCount = hasSelectedEvent ? 3 : 1;
4227
4419
  const mainChildCount = 1 + (timelineNodes.length > 0 ? 1 : 0);
4228
4420
  return [{
4229
4421
  childCount: mainChildCount,
4230
- className: 'ChatDebugViewDevtoolsMain',
4422
+ className: ChatDebugViewDevtoolsMain,
4231
4423
  type: Div
4232
4424
  }, ...timelineNodes, {
4233
4425
  childCount: splitChildCount,
4234
- className: 'ChatDebugViewDevtoolsSplit',
4426
+ className: ChatDebugViewDevtoolsSplit,
4235
4427
  type: Div
4236
4428
  }, {
4237
4429
  childCount: 1,
4238
4430
  className: eventsClassName,
4431
+ onKeyDown: HandleTableKeyDown,
4432
+ role: 'application',
4433
+ tabIndex: 0,
4239
4434
  type: Div
4240
4435
  }, ...tableNodes, ...sashNodes, ...detailsNodes];
4241
4436
  };
@@ -4243,11 +4438,13 @@ const getDevtoolsDom = (events, selectedEvent, selectedEventIndex, timelineEvent
4243
4438
  const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4244
4439
  return [{
4245
4440
  childCount: eventNodes.length === 0 ? 1 : eventNodes.length,
4246
- className: 'ChatDebugViewEvents',
4441
+ className: ChatDebugViewEvents,
4442
+ role: 'application',
4443
+ tabIndex: 0,
4247
4444
  type: Div
4248
4445
  }, ...(eventNodes.length === 0 ? [{
4249
4446
  childCount: 1,
4250
- className: errorMessage ? 'ChatDebugViewError' : 'ChatDebugViewEmpty',
4447
+ className: errorMessage ? ChatDebugViewError : ChatDebugViewEmpty,
4251
4448
  type: Div
4252
4449
  }, text(errorMessage || emptyMessage)] : eventNodes)];
4253
4450
  };
@@ -4255,18 +4452,18 @@ const getLegacyEventsDom = (errorMessage, emptyMessage, eventNodes) => {
4255
4452
  const getQuickFilterNodes = (eventCategoryFilter, eventCategoryFilterOptions) => {
4256
4453
  return [{
4257
4454
  childCount: eventCategoryFilterOptions.length,
4258
- className: 'ChatDebugViewQuickFilters',
4455
+ className: ChatDebugViewQuickFilters,
4259
4456
  type: Div
4260
4457
  }, ...eventCategoryFilterOptions.flatMap(option => {
4261
4458
  const isSelected = option.value === eventCategoryFilter;
4262
4459
  return [{
4263
4460
  childCount: 2,
4264
- className: `ChatDebugViewQuickFilterPill${isSelected ? ' ChatDebugViewQuickFilterPillSelected' : ''}`,
4461
+ className: joinClassNames(ChatDebugViewQuickFilterPill, isSelected && ChatDebugViewQuickFilterPillSelected),
4265
4462
  type: Label
4266
4463
  }, {
4267
4464
  checked: isSelected,
4268
4465
  childCount: 0,
4269
- className: 'ChatDebugViewQuickFilterInput',
4466
+ className: ChatDebugViewQuickFilterInput,
4270
4467
  inputType: 'radio',
4271
4468
  name: EventCategoryFilter,
4272
4469
  onChange: HandleInput,
@@ -4307,10 +4504,12 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, eve
4307
4504
  if (timelineFilterDescription) {
4308
4505
  filterDescriptionParts.push(timelineFilterDescription);
4309
4506
  }
4507
+ const hasTimelineFilter = Boolean(timelineFilterDescription);
4310
4508
  const hasFilterValue = filterDescriptionParts.length > 0;
4311
4509
  const filterDescription = filterDescriptionParts.join(' ');
4312
4510
  const noFilteredEventsMessage = `no events found matching ${filterDescription}`;
4313
- const emptyMessage = events.length === 0 && hasFilterValue ? noFilteredEventsMessage : 'No events have been found';
4511
+ const useNoToolCallEventsMessage = eventCategoryFilter === Tools && !trimmedFilterValue && !hasTimelineFilter;
4512
+ const emptyMessage = events.length === 0 && hasFilterValue ? useNoToolCallEventsMessage ? 'No tool call events.' : noFilteredEventsMessage : 'No events have been found';
4314
4513
  const safeSelectedEventIndex = selectedEventIndex === null || selectedEventIndex < 0 || selectedEventIndex >= events.length ? null : selectedEventIndex;
4315
4514
  const contentNodes = useDevtoolsLayout ? getDevtoolsDom(events, selectedEvent, safeSelectedEventIndex, timelineEvents, timelineStartSeconds, timelineEndSeconds, emptyMessage, timelineSelectionActive, timelineSelectionAnchorSeconds, timelineSelectionFocusSeconds, isDetailTab(selectedDetailTab) ? selectedDetailTab : Response) : getLegacyEventsDom(errorMessage, emptyMessage, events.flatMap(getEventNode));
4316
4515
  const quickFilterNodes = useDevtoolsLayout ? getQuickFilterNodes(eventCategoryFilter, eventCategoryFilterOptions) : [];
@@ -4318,7 +4517,7 @@ const getChatDebugViewDom = (errorMessage, filterValue, eventCategoryFilter, eve
4318
4517
  const rootChildCount = 2;
4319
4518
  return [{
4320
4519
  childCount: rootChildCount,
4321
- className: useDevtoolsLayout ? 'ChatDebugView ChatDebugView--devtools' : 'ChatDebugView',
4520
+ className: joinClassNames(ChatDebugView, useDevtoolsLayout && ChatDebugViewDevtools),
4322
4521
  type: Div
4323
4522
  }, ...debugViewTopDom, ...contentNodes];
4324
4523
  };
@@ -4385,7 +4584,7 @@ const render2 = (uid, diffResult) => {
4385
4584
  const renderEventListeners = () => {
4386
4585
  return [{
4387
4586
  name: HandleEventRowClick,
4388
- params: ['handleEventRowClick', 'event.target.dataset.index']
4587
+ params: ['handleEventRowClick', 'event.target.dataset.index', 'event.button']
4389
4588
  }, {
4390
4589
  name: HandleTableBodyContextMenu,
4391
4590
  params: ['handleTableBodyContextMenu'],
@@ -4537,7 +4736,9 @@ const commandMap = {
4537
4736
  'ChatDebug.create': create,
4538
4737
  'ChatDebug.diff2': diff2,
4539
4738
  'ChatDebug.getCommandIds': getCommandIds,
4739
+ 'ChatDebug.handleDetailsContextMenu': wrapCommand(handleDetailsContextMenu),
4540
4740
  'ChatDebug.handleEventRowClick': wrapCommand(handleEventRowClick),
4741
+ 'ChatDebug.handleHeaderContextMenu': wrapCommand(handleHeaderContextMenu),
4541
4742
  'ChatDebug.handleInput': wrapCommand(handleInput),
4542
4743
  'ChatDebug.handleSashPointerDown': wrapCommand(handleSashPointerDown),
4543
4744
  'ChatDebug.handleSashPointerMove': wrapCommand(handleSashPointerMove),