@tonyclaw/agent-inspector 2.1.16 → 2.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.output/cli.js +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-Ccrrcx1j.js → CompareDrawer-Fqqw5Lci.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-CP_RzwW9.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-C7axhr-l.js → ReplayDialog-hYhz_i6W.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-WOptg8j_.js → RequestAnatomy-CTuWbtwC.js} +1 -1
  7. package/.output/public/assets/{ResponseView-CSAcxh_M.js → ResponseView-CsZ7S5Gv.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-DJS5KhPx.js → StreamingChunkSequence-PIbJMotU.js} +1 -1
  9. package/.output/public/assets/_sessionId-CUGA9qU3.js +1 -0
  10. package/.output/public/assets/{index-DMPNh46t.js → index-F7mynwuH.js} +1 -1
  11. package/.output/public/assets/index-G4dTUobw.js +1 -0
  12. package/.output/public/assets/index-gvYkgMPF.css +1 -0
  13. package/.output/public/assets/{json-viewer-inX5QSa3.js → json-viewer-DCXtNmmD.js} +1 -1
  14. package/.output/public/assets/{main-aCAKYGSD.js → main-BJZf7_Ib.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +220 -202
  16. package/.output/server/{_sessionId-C0fhvQnn.mjs → _sessionId-DjSHDNh8.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-CO-Ti5sg.mjs → CompareDrawer-Dal52IFo.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-N4J7uBCX.mjs → ProxyViewerContainer-CVqFZGff.mjs} +1833 -1366
  19. package/.output/server/_ssr/{ReplayDialog-J1VxC0In.mjs → ReplayDialog-BOEgdMes.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-DBH-F3rq.mjs → RequestAnatomy-gch7_vCV.mjs} +2 -2
  21. package/.output/server/_ssr/{ResponseView-DLFwV7Hd.mjs → ResponseView-lEWPy-H4.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-BywmuND0.mjs → StreamingChunkSequence-DnudbJ1O.mjs} +2 -2
  23. package/.output/server/_ssr/{index-CStymIc0.mjs → index-Cza8zDrW.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-DlQu0N1x.mjs → json-viewer-C2lIVEX3.mjs} +3 -3
  26. package/.output/server/_ssr/{router-CQ4Zxhmr.mjs → router-bF9uzd1k.mjs} +939 -113
  27. package/.output/server/{_tanstack-start-manifest_v-BHeJBGfo.mjs → _tanstack-start-manifest_v-ErvW-mtr.mjs} +1 -1
  28. package/.output/server/index.mjs +74 -74
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +395 -64
  34. package/src/components/providers/ProviderCard.tsx +132 -31
  35. package/src/components/providers/ProviderForm.tsx +43 -11
  36. package/src/components/providers/ProvidersPanel.tsx +1 -1
  37. package/src/components/proxy-viewer/ConversationHeader.tsx +133 -112
  38. package/src/components/proxy-viewer/LogEntry.tsx +109 -26
  39. package/src/components/proxy-viewer/LogEntryHeader.tsx +339 -329
  40. package/src/components/proxy-viewer/ThreadConnector.tsx +49 -46
  41. package/src/components/proxy-viewer/TurnGroup.tsx +22 -62
  42. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  43. package/src/lib/providerTestContract.ts +56 -10
  44. package/src/lib/providerTestPrompt.ts +23 -1
  45. package/src/lib/upstreamUrl.ts +105 -1
  46. package/src/mcp/server.ts +275 -0
  47. package/src/mcp/toolHandlers.ts +381 -4
  48. package/src/proxy/logSearch.ts +3 -0
  49. package/src/proxy/store.ts +40 -15
  50. package/src/routes/api/logs.ts +8 -2
  51. package/src/routes/api/providers.$providerId.test.log.ts +85 -10
  52. package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +0 -106
  53. package/.output/public/assets/_sessionId-DUSalzKH.js +0 -1
  54. package/.output/public/assets/index-BPpA21dY.css +0 -1
  55. package/.output/public/assets/index-Bt0Az2I2.js +0 -1
@@ -218,6 +218,9 @@ export const LogEntryHeader = memo(function ({
218
218
  log.clientCwd !== null && log.clientCwd !== undefined && log.clientCwd !== "";
219
219
  const statusCue = STATUS_CUE_CLASSES[statusCategory];
220
220
  const statusLabel = statusCueLabel(statusCategory, log.responseStatus);
221
+ const showStatusBadge = statusCategory !== "success" || log.responseStatus !== 200;
222
+ const showStandaloneStreamingIndicator =
223
+ log.streaming && firstChunkLabel === null && tokenRateLabel === null;
221
224
 
222
225
  return (
223
226
  <TooltipProvider>
@@ -228,7 +231,7 @@ export const LogEntryHeader = memo(function ({
228
231
  data-nav-id={`log-${log.id}`}
229
232
  data-nav-action={expanded ? "collapse" : "expand"}
230
233
  className={cn(
231
- "group/header relative isolate flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 overflow-hidden px-3 py-2 cursor-pointer transition-[background-color,border-color,box-shadow]",
234
+ "group/header relative isolate flex min-w-0 cursor-pointer items-start gap-2 overflow-hidden px-3 py-2 transition-[background-color,border-color,box-shadow]",
232
235
  expanded
233
236
  ? "bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.10)]"
234
237
  : "bg-transparent hover:bg-white/[0.035]",
@@ -243,370 +246,377 @@ export const LogEntryHeader = memo(function ({
243
246
  }
244
247
  }}
245
248
  >
246
- {/* Request ID */}
247
- <span
248
- className={cn(
249
- "border border-border bg-muted/40 inline-flex h-8 shrink-0 items-center gap-2 rounded-md px-2 font-mono",
250
- statusCue.frame,
251
- )}
252
- >
253
- <span className={cn("size-1.5 rounded-full", statusCue.dot)} aria-hidden="true" />
254
- <span className="text-xs font-semibold tabular-nums" title={`Log ID ${String(log.id)}`}>
255
- #{displayNumber}
249
+ <span className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1">
250
+ {/* Request ID */}
251
+ <span
252
+ className={cn(
253
+ "border border-border bg-muted/40 inline-flex h-8 shrink-0 items-center gap-2 rounded-md px-2 font-mono",
254
+ statusCue.frame,
255
+ )}
256
+ >
257
+ <span className={cn("size-1.5 rounded-full", statusCue.dot)} aria-hidden="true" />
258
+ <span className="text-xs font-semibold tabular-nums" title={`Log ID ${String(log.id)}`}>
259
+ #{displayNumber}
260
+ </span>
256
261
  </span>
257
- </span>
258
262
 
259
- {/* Request start time */}
260
- <span className="border border-border bg-muted/40 flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground">
261
- <Clock className="size-3" />
262
- <span className="font-mono tabular-nums" title={log.timestamp}>
263
- {formatTimestamp(log.timestamp, timeDisplayFormat)}
263
+ {/* Request start time */}
264
+ <span className="border border-border bg-muted/40 flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground">
265
+ <Clock className="size-3" />
266
+ <span className="font-mono tabular-nums" title={log.timestamp}>
267
+ {formatTimestamp(log.timestamp, timeDisplayFormat)}
268
+ </span>
264
269
  </span>
265
- </span>
266
270
 
267
- {/* Response Status - only shown for non-200 or pending. Each category
271
+ {/* Response Status - only shown for non-200 or pending. Each category
268
272
  carries a distinct icon in addition to color so the meaning is
269
273
  legible without color perception. */}
270
- <Badge
271
- variant="outline"
272
- className={cn(
273
- "h-6 gap-1 rounded-md px-2 py-0 font-mono text-[10px] font-semibold uppercase tabular-nums shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
274
- STATUS_BADGE_CLASSES[statusCategory],
274
+ {showStatusBadge && (
275
+ <Badge
276
+ variant="outline"
277
+ className={cn(
278
+ "h-6 gap-1 rounded-md px-2 py-0 font-mono text-[10px] font-semibold uppercase tabular-nums shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
279
+ STATUS_BADGE_CLASSES[statusCategory],
280
+ )}
281
+ >
282
+ {statusCategory === "success" ? (
283
+ <Check className="size-3" aria-label="Success" />
284
+ ) : statusCategory === "server_error" ? (
285
+ <OctagonAlert className="size-3" aria-label="Server error" />
286
+ ) : statusCategory === "client_error" ? (
287
+ <AlertTriangle className="size-3" aria-label="Client error" />
288
+ ) : (
289
+ <Loader2 className="size-3 animate-spin" aria-label="Pending" />
290
+ )}
291
+ {statusLabel}
292
+ </Badge>
275
293
  )}
276
- >
277
- {statusCategory === "success" ? (
278
- <Check className="size-3" aria-label="Success" />
279
- ) : statusCategory === "server_error" ? (
280
- <OctagonAlert className="size-3" aria-label="Server error" />
281
- ) : statusCategory === "client_error" ? (
282
- <AlertTriangle className="size-3" aria-label="Client error" />
283
- ) : (
284
- <Loader2 className="size-3 animate-spin" aria-label="Pending" />
285
- )}
286
- {statusLabel}
287
- </Badge>
288
294
 
289
- {warningCount > 0 && (
290
- <Tooltip>
291
- <TooltipTrigger asChild>
292
- <Badge
293
- variant="outline"
294
- className="h-6 gap-1 rounded-md border-amber-300/30 bg-amber-300/[0.10] px-2 py-0 font-mono text-[10px] tabular-nums text-amber-100"
295
- >
296
- <AlertTriangle className="size-3" aria-label="Tool schema warnings" />
297
- {warningCount}
298
- </Badge>
299
- </TooltipTrigger>
300
- <TooltipContent>
301
- {warningCount === 1
302
- ? "1 tool schema warning"
303
- : `${warningCount} tool schema warnings`}
304
- </TooltipContent>
305
- </Tooltip>
306
- )}
295
+ {warningCount > 0 && (
296
+ <Tooltip>
297
+ <TooltipTrigger asChild>
298
+ <Badge
299
+ variant="outline"
300
+ className="h-6 gap-1 rounded-md border-amber-300/30 bg-amber-300/[0.10] px-2 py-0 font-mono text-[10px] tabular-nums text-amber-100"
301
+ >
302
+ <AlertTriangle className="size-3" aria-label="Tool schema warnings" />
303
+ {warningCount}
304
+ </Badge>
305
+ </TooltipTrigger>
306
+ <TooltipContent>
307
+ {warningCount === 1
308
+ ? "1 tool schema warning"
309
+ : `${warningCount} tool schema warnings`}
310
+ </TooltipContent>
311
+ </Tooltip>
312
+ )}
307
313
 
308
- {/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
314
+ {/* Elapsed time. Tooltip is only shown when this log exceeded the slow-
309
315
  response threshold - for normal-speed logs the elapsed value is
310
316
  self-explanatory, so we skip the tooltip wrapper to keep the row
311
317
  quiet. */}
312
- {log.elapsedMs !== null &&
313
- (isSlowResponse ? (
318
+ {log.elapsedMs !== null &&
319
+ (isSlowResponse ? (
320
+ <Tooltip>
321
+ <TooltipTrigger asChild>
322
+ <span
323
+ className={cn(
324
+ TELEMETRY_CHIP_CLASS,
325
+ "shrink-0 border-amber-300/25 text-amber-200",
326
+ )}
327
+ >
328
+ <Clock className="size-3" />
329
+ <span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
330
+ <AlertTriangle className="size-3" aria-label="Slow response" />
331
+ </span>
332
+ </TooltipTrigger>
333
+ <TooltipContent>
334
+ Slow response: {formatElapsed(log.elapsedMs)} exceeds{" "}
335
+ {formatElapsed(slowResponseThresholdSeconds * 1000)}
336
+ </TooltipContent>
337
+ </Tooltip>
338
+ ) : (
339
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
340
+ <Clock className="size-3" />
341
+ <span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
342
+ </span>
343
+ ))}
344
+
345
+ {/* Streaming timing */}
346
+ {log.streaming && firstChunkLabel !== null && (
347
+ <Tooltip>
348
+ <TooltipTrigger asChild>
349
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
350
+ <Radio className="size-3" />
351
+ <span className="font-mono tabular-nums">first {firstChunkLabel}</span>
352
+ </span>
353
+ </TooltipTrigger>
354
+ <TooltipContent>Time to first streaming chunk</TooltipContent>
355
+ </Tooltip>
356
+ )}
357
+ {log.streaming && tokenRateLabel !== null && (
314
358
  <Tooltip>
315
359
  <TooltipTrigger asChild>
360
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
361
+ <Gauge className="size-3" />
362
+ <span className="font-mono tabular-nums">{tokenRateLabel}</span>
363
+ </span>
364
+ </TooltipTrigger>
365
+ <TooltipContent>Output token throughput after first chunk</TooltipContent>
366
+ </Tooltip>
367
+ )}
368
+
369
+ {/* Token counts */}
370
+ {hasTokens && (
371
+ <span className={cn(TELEMETRY_CHIP_CLASS, "min-w-0 sm:shrink-0")}>
372
+ <Zap className="size-3 text-muted-foreground" />
373
+ <span className="flex min-w-0 flex-wrap items-center gap-x-1 font-mono tabular-nums">
316
374
  <span
317
375
  className={cn(
318
- TELEMETRY_CHIP_CLASS,
319
- "shrink-0 border-amber-300/25 text-amber-200",
376
+ "whitespace-nowrap",
377
+ log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
320
378
  )}
321
379
  >
322
- <Clock className="size-3" />
323
- <span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
324
- <AlertTriangle className="size-3" aria-label="Slow response" />
380
+ IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
381
+ </span>
382
+ <span className="text-muted-foreground">/</span>
383
+ <span
384
+ className={cn(
385
+ "whitespace-nowrap",
386
+ log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
387
+ )}
388
+ >
389
+ OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
325
390
  </span>
326
- </TooltipTrigger>
327
- <TooltipContent>
328
- Slow response: {formatElapsed(log.elapsedMs)} exceeds{" "}
329
- {formatElapsed(slowResponseThresholdSeconds * 1000)}
330
- </TooltipContent>
331
- </Tooltip>
332
- ) : (
333
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
334
- <Clock className="size-3" />
335
- <span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
336
- </span>
337
- ))}
338
-
339
- {/* Streaming timing */}
340
- {log.streaming && firstChunkLabel !== null && (
341
- <Tooltip>
342
- <TooltipTrigger asChild>
343
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
344
- <Radio className="size-3" />
345
- <span className="font-mono tabular-nums">first {firstChunkLabel}</span>
346
- </span>
347
- </TooltipTrigger>
348
- <TooltipContent>Time to first streaming chunk</TooltipContent>
349
- </Tooltip>
350
- )}
351
- {log.streaming && tokenRateLabel !== null && (
352
- <Tooltip>
353
- <TooltipTrigger asChild>
354
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
355
- <Gauge className="size-3" />
356
- <span className="font-mono tabular-nums">{tokenRateLabel}</span>
357
- </span>
358
- </TooltipTrigger>
359
- <TooltipContent>Output token throughput after first chunk</TooltipContent>
360
- </Tooltip>
361
- )}
362
-
363
- {/* Token counts */}
364
- {hasTokens && (
365
- <span className={cn(TELEMETRY_CHIP_CLASS, "min-w-0 sm:shrink-0")}>
366
- <Zap className="size-3 text-muted-foreground" />
367
- <span className="flex min-w-0 flex-wrap items-center gap-x-1 font-mono tabular-nums">
368
- <span
369
- className={cn(
370
- "whitespace-nowrap",
371
- log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
372
- )}
373
- >
374
- IN {log.inputTokens !== null ? formatTokens(log.inputTokens) : "n/a"}
375
- </span>
376
- <span className="text-muted-foreground">/</span>
377
- <span
378
- className={cn(
379
- "whitespace-nowrap",
380
- log.outputTokens !== null ? "text-amber-400" : "text-muted-foreground",
381
- )}
382
- >
383
- OUT {log.outputTokens !== null ? formatTokens(log.outputTokens) : "n/a"}
384
391
  </span>
385
392
  </span>
386
- </span>
387
- )}
388
- {/* Cache tokens */}
389
- {log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
390
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
391
- <CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
392
- <span className="font-mono tabular-nums text-cyan-300">
393
- KV Cache +{formatTokens(log.cacheCreationInputTokens)}
393
+ )}
394
+ {/* Cache tokens */}
395
+ {log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
396
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
397
+ <CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
398
+ <span className="font-mono tabular-nums text-cyan-300">
399
+ KV Cache +{formatTokens(log.cacheCreationInputTokens)}
400
+ </span>
394
401
  </span>
395
- </span>
396
- )}
397
- {log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
398
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
399
- <CacheTrendIndicator trend={cacheTrend?.read ?? null} />
400
- <span className="font-mono tabular-nums text-purple-400">
401
- KV Cache ~{formatTokens(log.cacheReadInputTokens)}
402
+ )}
403
+ {log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
404
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
405
+ <CacheTrendIndicator trend={cacheTrend?.read ?? null} />
406
+ <span className="font-mono tabular-nums text-purple-400">
407
+ KV Cache ~{formatTokens(log.cacheReadInputTokens)}
408
+ </span>
402
409
  </span>
403
- </span>
404
- )}
405
-
406
- {/* Message count */}
407
- {messageCount !== null && (
408
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
409
- <MessageSquare className="size-3" />
410
- <span className="font-mono tabular-nums">{messageCount}</span>
411
- </span>
412
- )}
410
+ )}
413
411
 
414
- {/* Tool count */}
415
- {toolCount !== null && (
416
- <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
417
- <Wrench className="size-3" />
418
- <span className="font-mono tabular-nums">{toolCount}</span>
419
- </span>
420
- )}
412
+ {/* Message count */}
413
+ {messageCount !== null && (
414
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
415
+ <MessageSquare className="size-3" />
416
+ <span className="font-mono tabular-nums">{messageCount}</span>
417
+ </span>
418
+ )}
421
419
 
422
- {/* Origin */}
423
- {log.origin !== null && (
424
- <span
425
- className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-muted-foreground xl:flex")}
426
- title={`Origin: ${log.origin}`}
427
- >
428
- <Globe className="size-3" />
429
- <span className="font-mono tabular-nums truncate max-w-[120px]" title={log.origin}>
430
- {log.origin}
420
+ {/* Tool count */}
421
+ {toolCount !== null && (
422
+ <span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
423
+ <Wrench className="size-3" />
424
+ <span className="font-mono tabular-nums">{toolCount}</span>
431
425
  </span>
432
- </span>
433
- )}
426
+ )}
434
427
 
435
- {/* Client info (PID + project folder) */}
436
- {showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
437
- <Tooltip>
438
- <TooltipTrigger asChild>
439
- <span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
440
- <FileTerminal className="size-3" />
441
- {hasClientProjectFolder ? (
442
- <span className="font-mono tabular-nums">{log.clientProjectFolder}</span>
443
- ) : (
444
- <span className="font-mono tabular-nums">PID {log.clientPid}</span>
445
- )}
428
+ {/* Origin */}
429
+ {log.origin !== null && (
430
+ <span
431
+ className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-muted-foreground xl:flex")}
432
+ title={`Origin: ${log.origin}`}
433
+ >
434
+ <Globe className="size-3" />
435
+ <span className="font-mono tabular-nums truncate max-w-[120px]" title={log.origin}>
436
+ {log.origin}
446
437
  </span>
447
- </TooltipTrigger>
448
- <TooltipContent>
449
- {hasClientCwd
450
- ? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}`
451
- : `Process ID: ${log.clientPid ?? "?"}`}
452
- </TooltipContent>
453
- </Tooltip>
454
- )}
438
+ </span>
439
+ )}
455
440
 
456
- {/* Streaming indicator */}
457
- {log.streaming && (
458
- <Tooltip>
459
- <TooltipTrigger asChild>
460
- <Radio className="size-3 text-muted-foreground/60 shrink-0" />
461
- </TooltipTrigger>
462
- <TooltipContent>Request used SSE streaming</TooltipContent>
463
- </Tooltip>
464
- )}
441
+ {/* Client info (PID + project folder) */}
442
+ {showClientMetadata && (hasClientPid || hasClientProjectFolder) && (
443
+ <Tooltip>
444
+ <TooltipTrigger asChild>
445
+ <span
446
+ className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}
447
+ >
448
+ <FileTerminal className="size-3" />
449
+ {hasClientProjectFolder ? (
450
+ <span className="font-mono tabular-nums">{log.clientProjectFolder}</span>
451
+ ) : (
452
+ <span className="font-mono tabular-nums">PID {log.clientPid}</span>
453
+ )}
454
+ </span>
455
+ </TooltipTrigger>
456
+ <TooltipContent>
457
+ {hasClientCwd
458
+ ? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}`
459
+ : `Process ID: ${log.clientPid ?? "?"}`}
460
+ </TooltipContent>
461
+ </Tooltip>
462
+ )}
465
463
 
466
- {/* Spacer */}
467
- <span className="flex-1 min-w-0" />
464
+ {/* Streaming indicator */}
465
+ {showStandaloneStreamingIndicator && (
466
+ <Tooltip>
467
+ <TooltipTrigger asChild>
468
+ <Radio className="size-3 text-muted-foreground/60 shrink-0" />
469
+ </TooltipTrigger>
470
+ <TooltipContent>Request used SSE streaming</TooltipContent>
471
+ </Tooltip>
472
+ )}
473
+ </span>
468
474
 
469
- {/* Header actions - only when expanded, so the collapsed view stays
475
+ <span className="flex shrink-0 items-start gap-1.5">
476
+ {/* Header actions - only when expanded, so the collapsed view stays
470
477
  compact. Buttons stop propagation so they don't toggle the log. */}
471
- {expanded && (
478
+ {expanded && (
479
+ <span
480
+ className="flex items-center gap-1.5 shrink-0"
481
+ onClick={(e) => e.stopPropagation()}
482
+ onKeyDown={(e) => e.stopPropagation()}
483
+ >
484
+ {tabActions !== undefined &&
485
+ activeTab !== undefined &&
486
+ (() => {
487
+ const action = tabActions[activeTab];
488
+ if (action === undefined) return null;
489
+ return (
490
+ <>
491
+ {action.expansion !== null && (
492
+ <Tooltip>
493
+ <TooltipTrigger asChild>
494
+ <Button
495
+ variant="outline"
496
+ size="icon"
497
+ className={HEADER_ACTION_BUTTON_CLASS}
498
+ onClick={action.expansion.onToggle}
499
+ disabled={action.expansion.isPending}
500
+ aria-pressed={action.expansion.isExpanded}
501
+ aria-label={
502
+ action.expansion.isExpanded
503
+ ? "Collapse all JSON"
504
+ : "Expand all JSON"
505
+ }
506
+ >
507
+ {action.expansion.isExpanded ? (
508
+ <ChevronsUp className="size-3.5" />
509
+ ) : (
510
+ <ChevronsDown className="size-3.5" />
511
+ )}
512
+ </Button>
513
+ </TooltipTrigger>
514
+ <TooltipContent>
515
+ {action.expansion.isExpanded
516
+ ? "Collapse all JSON nodes"
517
+ : "Expand all JSON nodes"}
518
+ </TooltipContent>
519
+ </Tooltip>
520
+ )}
521
+ {action.diffWithRaw !== undefined && (
522
+ <Tooltip>
523
+ <TooltipTrigger asChild>
524
+ <Button
525
+ variant="outline"
526
+ size="icon"
527
+ className={cn(
528
+ HEADER_ACTION_BUTTON_CLASS,
529
+ action.diffWithRaw.active &&
530
+ "border-cyan-300/35 bg-cyan-400/15 text-cyan-100",
531
+ )}
532
+ onClick={action.diffWithRaw.onToggle}
533
+ aria-pressed={action.diffWithRaw.active}
534
+ aria-label={
535
+ action.diffWithRaw.active ? "Hide raw diff" : "Diff with raw"
536
+ }
537
+ >
538
+ <FileDiff className="size-3.5" />
539
+ </Button>
540
+ </TooltipTrigger>
541
+ <TooltipContent>
542
+ {action.diffWithRaw.active
543
+ ? "Hide diff with raw request"
544
+ : "Show diff between displayed and raw request body"}
545
+ </TooltipContent>
546
+ </Tooltip>
547
+ )}
548
+ {action.diffWithPrevious !== undefined && (
549
+ <Tooltip>
550
+ <TooltipTrigger asChild>
551
+ <Button
552
+ variant="outline"
553
+ size="icon"
554
+ className={HEADER_ACTION_BUTTON_CLASS}
555
+ onClick={action.diffWithPrevious}
556
+ aria-label="Diff with previous"
557
+ >
558
+ <History className="size-3.5" />
559
+ </Button>
560
+ </TooltipTrigger>
561
+ <TooltipContent>
562
+ Compare this request with the immediately preceding one
563
+ </TooltipContent>
564
+ </Tooltip>
565
+ )}
566
+ {action.copyText !== null && (
567
+ <Tooltip>
568
+ <TooltipTrigger asChild>
569
+ <Button
570
+ variant="outline"
571
+ size="icon"
572
+ className={HEADER_ACTION_BUTTON_CLASS}
573
+ onClick={action.onCopy}
574
+ aria-label={action.copyCopied ? "Copied" : action.copyLabel}
575
+ >
576
+ {action.copyCopied ? (
577
+ <Check className="size-3.5 text-cyan-300" />
578
+ ) : (
579
+ <Copy className="size-3.5" />
580
+ )}
581
+ </Button>
582
+ </TooltipTrigger>
583
+ <TooltipContent>
584
+ {action.copyCopied ? "Copied to clipboard" : action.copyLabel}
585
+ </TooltipContent>
586
+ </Tooltip>
587
+ )}
588
+ </>
589
+ );
590
+ })()}
591
+ {onReplay !== undefined && (
592
+ <Tooltip>
593
+ <TooltipTrigger asChild>
594
+ <Button
595
+ variant="outline"
596
+ size="icon"
597
+ className={HEADER_ACTION_BUTTON_CLASS}
598
+ onClick={onReplay}
599
+ aria-label="Replay request"
600
+ >
601
+ <RotateCcw className="size-3.5" />
602
+ </Button>
603
+ </TooltipTrigger>
604
+ <TooltipContent>Re-send this request to the provider</TooltipContent>
605
+ </Tooltip>
606
+ )}
607
+ </span>
608
+ )}
609
+
472
610
  <span
473
- className="flex items-center gap-1.5 shrink-0"
474
- onClick={(e) => e.stopPropagation()}
475
- onKeyDown={(e) => e.stopPropagation()}
611
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200"
612
+ aria-hidden="true"
476
613
  >
477
- {tabActions !== undefined &&
478
- activeTab !== undefined &&
479
- (() => {
480
- const action = tabActions[activeTab];
481
- if (action === undefined) return null;
482
- return (
483
- <>
484
- {action.expansion !== null && (
485
- <Tooltip>
486
- <TooltipTrigger asChild>
487
- <Button
488
- variant="outline"
489
- size="icon"
490
- className={HEADER_ACTION_BUTTON_CLASS}
491
- onClick={action.expansion.onToggle}
492
- disabled={action.expansion.isPending}
493
- aria-pressed={action.expansion.isExpanded}
494
- aria-label={
495
- action.expansion.isExpanded ? "Collapse all JSON" : "Expand all JSON"
496
- }
497
- >
498
- {action.expansion.isExpanded ? (
499
- <ChevronsUp className="size-3.5" />
500
- ) : (
501
- <ChevronsDown className="size-3.5" />
502
- )}
503
- </Button>
504
- </TooltipTrigger>
505
- <TooltipContent>
506
- {action.expansion.isExpanded
507
- ? "Collapse all JSON nodes"
508
- : "Expand all JSON nodes"}
509
- </TooltipContent>
510
- </Tooltip>
511
- )}
512
- {action.diffWithRaw !== undefined && (
513
- <Tooltip>
514
- <TooltipTrigger asChild>
515
- <Button
516
- variant="outline"
517
- size="icon"
518
- className={cn(
519
- HEADER_ACTION_BUTTON_CLASS,
520
- action.diffWithRaw.active &&
521
- "border-cyan-300/35 bg-cyan-400/15 text-cyan-100",
522
- )}
523
- onClick={action.diffWithRaw.onToggle}
524
- aria-pressed={action.diffWithRaw.active}
525
- aria-label={
526
- action.diffWithRaw.active ? "Hide raw diff" : "Diff with raw"
527
- }
528
- >
529
- <FileDiff className="size-3.5" />
530
- </Button>
531
- </TooltipTrigger>
532
- <TooltipContent>
533
- {action.diffWithRaw.active
534
- ? "Hide diff with raw request"
535
- : "Show diff between displayed and raw request body"}
536
- </TooltipContent>
537
- </Tooltip>
538
- )}
539
- {action.diffWithPrevious !== undefined && (
540
- <Tooltip>
541
- <TooltipTrigger asChild>
542
- <Button
543
- variant="outline"
544
- size="icon"
545
- className={HEADER_ACTION_BUTTON_CLASS}
546
- onClick={action.diffWithPrevious}
547
- aria-label="Diff with previous"
548
- >
549
- <History className="size-3.5" />
550
- </Button>
551
- </TooltipTrigger>
552
- <TooltipContent>
553
- Compare this request with the immediately preceding one
554
- </TooltipContent>
555
- </Tooltip>
556
- )}
557
- {action.copyText !== null && (
558
- <Tooltip>
559
- <TooltipTrigger asChild>
560
- <Button
561
- variant="outline"
562
- size="icon"
563
- className={HEADER_ACTION_BUTTON_CLASS}
564
- onClick={action.onCopy}
565
- aria-label={action.copyCopied ? "Copied" : action.copyLabel}
566
- >
567
- {action.copyCopied ? (
568
- <Check className="size-3.5 text-cyan-300" />
569
- ) : (
570
- <Copy className="size-3.5" />
571
- )}
572
- </Button>
573
- </TooltipTrigger>
574
- <TooltipContent>
575
- {action.copyCopied ? "Copied to clipboard" : action.copyLabel}
576
- </TooltipContent>
577
- </Tooltip>
578
- )}
579
- </>
580
- );
581
- })()}
582
- {onReplay !== undefined && (
583
- <Tooltip>
584
- <TooltipTrigger asChild>
585
- <Button
586
- variant="outline"
587
- size="icon"
588
- className={HEADER_ACTION_BUTTON_CLASS}
589
- onClick={onReplay}
590
- aria-label="Replay request"
591
- >
592
- <RotateCcw className="size-3.5" />
593
- </Button>
594
- </TooltipTrigger>
595
- <TooltipContent>Re-send this request to the provider</TooltipContent>
596
- </Tooltip>
614
+ {expanded ? (
615
+ <ChevronDown className="size-4 shrink-0" />
616
+ ) : (
617
+ <ChevronRight className="size-4 shrink-0" />
597
618
  )}
598
619
  </span>
599
- )}
600
-
601
- <span
602
- className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200"
603
- aria-hidden="true"
604
- >
605
- {expanded ? (
606
- <ChevronDown className="size-4 shrink-0" />
607
- ) : (
608
- <ChevronRight className="size-4 shrink-0" />
609
- )}
610
620
  </span>
611
621
  </div>
612
622
  </TooltipProvider>