@tonyclaw/llm-inspector 1.15.1 → 1.16.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.
Files changed (39) hide show
  1. package/.output/cli.js +1 -0
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/index-BmkN9DxE.js +107 -0
  4. package/.output/public/assets/index-DPe3eOih.css +1 -0
  5. package/.output/public/assets/{main-C2-qvdhC.js → main-BjnjXVBU.js} +1 -1
  6. package/.output/server/_ssr/{index-C001qcnM.mjs → index-BIOEVAzU.mjs} +216 -180
  7. package/.output/server/_ssr/index.mjs +2 -2
  8. package/.output/server/_ssr/{router-D7aEu4dR.mjs → router-THS9ptvu.mjs} +392 -170
  9. package/.output/server/{_tanstack-start-manifest_v-BXMwlSXD.mjs → _tanstack-start-manifest_v-BYhN7q_z.mjs} +1 -1
  10. package/.output/server/index.mjs +27 -27
  11. package/package.json +1 -1
  12. package/src/cli.ts +1 -0
  13. package/src/components/ProxyViewer.tsx +16 -54
  14. package/src/components/ProxyViewerContainer.tsx +121 -77
  15. package/src/components/providers/ImportWizardDialog.tsx +27 -3
  16. package/src/components/proxy-viewer/ConversationGroup.tsx +3 -3
  17. package/src/components/proxy-viewer/ConversationHeader.tsx +1 -1
  18. package/src/components/proxy-viewer/LogEntry.tsx +184 -171
  19. package/src/components/proxy-viewer/LogEntryHeader.tsx +126 -137
  20. package/src/components/proxy-viewer/ResponseView.tsx +3 -2
  21. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +3 -3
  22. package/src/components/proxy-viewer/TurnGroup.tsx +4 -4
  23. package/src/components/proxy-viewer/diff/DiffView.tsx +5 -3
  24. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -9
  25. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +3 -3
  26. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +7 -3
  27. package/src/components/ui/json-viewer.tsx +3 -3
  28. package/src/lib/objectUtils.ts +22 -0
  29. package/src/proxy/claudeCodeStrip.ts +5 -8
  30. package/src/proxy/logIndex.ts +58 -43
  31. package/src/proxy/logger.ts +51 -27
  32. package/src/proxy/openaiOrphanToolStrip.ts +11 -17
  33. package/src/proxy/providerImporters.ts +245 -19
  34. package/src/proxy/providers.ts +20 -7
  35. package/src/proxy/schemas.ts +5 -9
  36. package/src/proxy/socketTracker.ts +109 -78
  37. package/src/proxy/store.ts +52 -83
  38. package/.output/public/assets/index-BIUbzgJN.css +0 -1
  39. package/.output/public/assets/index-TwYgzIL4.js +0 -107
@@ -209,6 +209,7 @@ export const LogEntry = memo(function ({
209
209
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
210
210
  const [headersDiff, setHeadersDiff] = useState<boolean>(false);
211
211
  const [requestDiff, setRequestDiff] = useState<boolean>(false);
212
+ const [activeTab, setActiveTab] = useState("request");
212
213
  const resolvedFormat = resolveLogFormat(log);
213
214
  const adapter = getLogFormatAdapter(resolvedFormat);
214
215
  const requestAnalysis = useMemo(
@@ -232,7 +233,7 @@ export const LogEntry = memo(function ({
232
233
 
233
234
  return (
234
235
  <TooltipProvider>
235
- <div className="border border-border rounded-lg mb-1.5 overflow-hidden">
236
+ <div className="border border-border rounded-lg mb-1 overflow-hidden">
236
237
  <LogEntryHeader
237
238
  log={log}
238
239
  messageCount={requestAnalysis.messageCount}
@@ -245,7 +246,7 @@ export const LogEntry = memo(function ({
245
246
 
246
247
  {expanded && (
247
248
  <div onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
248
- <Tabs defaultValue="request">
249
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
249
250
  <TabsList className="mx-4 mt-2">
250
251
  {viewMode === "full" && (
251
252
  <Tooltip>
@@ -289,42 +290,65 @@ export const LogEntry = memo(function ({
289
290
 
290
291
  {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
291
292
  <TabsContent value="raw-request">
292
- <div className="px-4 py-3">
293
- <div className="flex justify-end mb-2">
294
- <CopyButton
295
- text={log.rawRequestBody}
296
- label="Copy Raw Request"
297
- copied={rawRequestCopy.copied}
298
- onCopy={rawRequestCopy.copy}
299
- />
293
+ {activeTab === "raw-request" && (
294
+ <div className="px-4 py-3">
295
+ <div className="flex justify-end mb-2">
296
+ <CopyButton
297
+ text={log.rawRequestBody}
298
+ label="Copy Raw Request"
299
+ copied={rawRequestCopy.copied}
300
+ onCopy={rawRequestCopy.copy}
301
+ />
302
+ </div>
303
+ {log.rawRequestBody !== null ? (
304
+ <JsonViewerFromString text={log.rawRequestBody} defaultExpandDepth={1} />
305
+ ) : (
306
+ <p className="text-xs text-muted-foreground italic">No request body</p>
307
+ )}
300
308
  </div>
301
- {log.rawRequestBody !== null ? (
302
- <JsonViewerFromString text={log.rawRequestBody} defaultExpandDepth={1} />
303
- ) : (
304
- <p className="text-xs text-muted-foreground italic">No request body</p>
305
- )}
306
- </div>
309
+ )}
307
310
  </TabsContent>
308
311
  )}
309
312
 
310
313
  <TabsContent value="request">
311
- <div className="px-4 py-3">
312
- <div className="flex justify-end gap-2 mb-2">
313
- {shouldShowRequestDiffButton(
314
- resolvedFormat,
315
- viewMode,
316
- strip,
317
- log.rawRequestBody !== null,
318
- ) && (
319
- <DiffToggleButton
320
- active={requestDiff}
321
- onClick={(e) => {
322
- e.stopPropagation();
323
- setRequestDiff(!requestDiff);
324
- }}
325
- />
326
- )}
327
- {onCompareWithPrevious !== undefined && (
314
+ {activeTab === "request" && (
315
+ <div className="px-4 py-3">
316
+ <div className="flex justify-end gap-2 mb-2">
317
+ {shouldShowRequestDiffButton(
318
+ resolvedFormat,
319
+ viewMode,
320
+ strip,
321
+ log.rawRequestBody !== null,
322
+ ) && (
323
+ <DiffToggleButton
324
+ active={requestDiff}
325
+ onClick={(e) => {
326
+ e.stopPropagation();
327
+ setRequestDiff(!requestDiff);
328
+ }}
329
+ />
330
+ )}
331
+ {onCompareWithPrevious !== undefined && (
332
+ <Tooltip>
333
+ <TooltipTrigger asChild>
334
+ <Button
335
+ variant="outline"
336
+ size="sm"
337
+ className="h-7 text-xs"
338
+ onClick={(e) => {
339
+ e.stopPropagation();
340
+ onCompareWithPrevious(log);
341
+ }}
342
+ >
343
+ <GitCompareArrows className="size-3 mr-1" />
344
+ Diff with Previous
345
+ </Button>
346
+ </TooltipTrigger>
347
+ <TooltipContent>
348
+ Compare this request with the immediately preceding one
349
+ </TooltipContent>
350
+ </Tooltip>
351
+ )}
328
352
  <Tooltip>
329
353
  <TooltipTrigger asChild>
330
354
  <Button
@@ -333,172 +357,161 @@ export const LogEntry = memo(function ({
333
357
  className="h-7 text-xs"
334
358
  onClick={(e) => {
335
359
  e.stopPropagation();
336
- onCompareWithPrevious(log);
360
+ setReplayOpen(true);
337
361
  }}
338
362
  >
339
- <GitCompareArrows className="size-3 mr-1" />
340
- Diff with Previous
363
+ <RotateCcw className="size-3 mr-1" />
364
+ Replay
341
365
  </Button>
342
366
  </TooltipTrigger>
343
- <TooltipContent>
344
- Compare this request with the immediately preceding one
345
- </TooltipContent>
367
+ <TooltipContent>Re-send this request to the provider</TooltipContent>
346
368
  </Tooltip>
369
+ <CopyButton
370
+ text={displayedRequestBody}
371
+ label="Copy Request"
372
+ copied={requestCopy.copied}
373
+ onCopy={requestCopy.copy}
374
+ />
375
+ </div>
376
+ {requestDiff ? (
377
+ <RequestDiffContent
378
+ rawBody={log.rawRequestBody}
379
+ displayedBody={displayedRequestBody}
380
+ emptyLabel="No transformation applied — raw and sent request bodies are identical."
381
+ />
382
+ ) : displayedRequestBody !== null ? (
383
+ <JsonViewerFromString text={displayedRequestBody} defaultExpandDepth={1} />
384
+ ) : (
385
+ <p className="text-xs text-muted-foreground italic">No request body</p>
347
386
  )}
348
- <Tooltip>
349
- <TooltipTrigger asChild>
350
- <Button
351
- variant="outline"
352
- size="sm"
353
- className="h-7 text-xs"
354
- onClick={(e) => {
355
- e.stopPropagation();
356
- setReplayOpen(true);
357
- }}
358
- >
359
- <RotateCcw className="size-3 mr-1" />
360
- Replay
361
- </Button>
362
- </TooltipTrigger>
363
- <TooltipContent>Re-send this request to the provider</TooltipContent>
364
- </Tooltip>
365
- <CopyButton
366
- text={displayedRequestBody}
367
- label="Copy Request"
368
- copied={requestCopy.copied}
369
- onCopy={requestCopy.copy}
370
- />
371
387
  </div>
372
- {requestDiff ? (
373
- <RequestDiffContent
374
- rawBody={log.rawRequestBody}
375
- displayedBody={displayedRequestBody}
376
- emptyLabel="No transformation applied — raw and sent request bodies are identical."
377
- />
378
- ) : displayedRequestBody !== null ? (
379
- <JsonViewerFromString text={displayedRequestBody} defaultExpandDepth={1} />
380
- ) : (
381
- <p className="text-xs text-muted-foreground italic">No request body</p>
382
- )}
383
- </div>
388
+ )}
384
389
  </TabsContent>
385
390
 
386
391
  {viewMode === "full" && (
387
392
  <TabsContent value="headers">
388
- <div className="px-4 py-3">
389
- <div className="flex justify-end gap-2 mb-2">
390
- {shouldShowHeadersDiffButton(
391
- viewMode,
392
- log.rawHeaders !== undefined && Object.keys(log.rawHeaders).length > 0,
393
- ) && (
394
- <DiffToggleButton
395
- active={headersDiff}
396
- onClick={(e) => {
397
- e.stopPropagation();
398
- setHeadersDiff(!headersDiff);
399
- }}
393
+ {activeTab === "headers" && (
394
+ <div className="px-4 py-3">
395
+ <div className="flex justify-end gap-2 mb-2">
396
+ {shouldShowHeadersDiffButton(
397
+ viewMode,
398
+ log.rawHeaders !== undefined && Object.keys(log.rawHeaders).length > 0,
399
+ ) && (
400
+ <DiffToggleButton
401
+ active={headersDiff}
402
+ onClick={(e) => {
403
+ e.stopPropagation();
404
+ setHeadersDiff(!headersDiff);
405
+ }}
406
+ />
407
+ )}
408
+ </div>
409
+ {headersDiff ? (
410
+ <HeadersDiffContent
411
+ rawHeaders={log.rawHeaders}
412
+ headers={log.headers}
413
+ emptyLabel="No transformation applied — raw and processed headers are identical."
400
414
  />
415
+ ) : log.headers && Object.keys(log.headers).length > 0 ? (
416
+ <div className="space-y-1 font-mono text-xs">
417
+ {Object.entries(log.headers)
418
+ .sort(([a], [b]) => a.localeCompare(b))
419
+ .map(([key, value]) => (
420
+ <div key={key} className="flex gap-2">
421
+ <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
422
+ {key}:
423
+ </span>
424
+ <span className="text-muted-foreground truncate" title={value}>
425
+ {value}
426
+ </span>
427
+ </div>
428
+ ))}
429
+ </div>
430
+ ) : (
431
+ <p className="text-xs text-muted-foreground italic">No headers captured</p>
401
432
  )}
402
433
  </div>
403
- {headersDiff ? (
404
- <HeadersDiffContent
405
- rawHeaders={log.rawHeaders}
406
- headers={log.headers}
407
- emptyLabel="No transformation applied — raw and processed headers are identical."
408
- />
409
- ) : log.headers && Object.keys(log.headers).length > 0 ? (
410
- <div className="space-y-1 font-mono text-xs">
411
- {Object.entries(log.headers)
412
- .sort(([a], [b]) => a.localeCompare(b))
413
- .map(([key, value]) => (
414
- <div key={key} className="flex gap-2">
415
- <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
416
- {key}:
417
- </span>
418
- <span className="text-muted-foreground truncate" title={value}>
419
- {value}
420
- </span>
421
- </div>
422
- ))}
423
- </div>
424
- ) : (
425
- <p className="text-xs text-muted-foreground italic">No headers captured</p>
426
- )}
427
- </div>
434
+ )}
428
435
  </TabsContent>
429
436
  )}
430
437
 
431
438
  {viewMode === "full" && (
432
439
  <TabsContent value="raw-headers">
433
- <div className="px-4 py-3">
434
- {log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
435
- <div className="space-y-1 font-mono text-xs">
436
- {Object.entries(log.rawHeaders)
437
- .sort(([a], [b]) => a.localeCompare(b))
438
- .map(([key, value]) => (
439
- <div key={key} className="flex gap-2">
440
- <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
441
- {key}:
442
- </span>
443
- <span className="text-muted-foreground truncate" title={value}>
444
- {value}
445
- </span>
446
- </div>
447
- ))}
448
- </div>
449
- ) : (
450
- <p className="text-xs text-muted-foreground italic">
451
- No raw headers captured
452
- </p>
453
- )}
454
- </div>
440
+ {activeTab === "raw-headers" && (
441
+ <div className="px-4 py-3">
442
+ {log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? (
443
+ <div className="space-y-1 font-mono text-xs">
444
+ {Object.entries(log.rawHeaders)
445
+ .sort(([a], [b]) => a.localeCompare(b))
446
+ .map(([key, value]) => (
447
+ <div key={key} className="flex gap-2">
448
+ <span className="text-blue-600 dark:text-blue-400 font-semibold shrink-0">
449
+ {key}:
450
+ </span>
451
+ <span className="text-muted-foreground truncate" title={value}>
452
+ {value}
453
+ </span>
454
+ </div>
455
+ ))}
456
+ </div>
457
+ ) : (
458
+ <p className="text-xs text-muted-foreground italic">
459
+ No raw headers captured
460
+ </p>
461
+ )}
462
+ </div>
463
+ )}
455
464
  </TabsContent>
456
465
  )}
457
466
 
458
467
  <TabsContent value="raw">
459
- <div className="px-4 py-3 space-y-3">
460
- {log.error !== undefined && log.error !== null && (
461
- <div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
462
- <div className="font-semibold text-destructive mb-1">SSE Error</div>
463
- <div className="text-muted-foreground font-mono">{log.error}</div>
468
+ {activeTab === "raw" && (
469
+ <div className="px-4 py-3 space-y-3">
470
+ {log.error !== undefined && log.error !== null && (
471
+ <div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
472
+ <div className="font-semibold text-destructive mb-1">SSE Error</div>
473
+ <div className="text-muted-foreground font-mono">{log.error}</div>
474
+ </div>
475
+ )}
476
+ <div className="flex justify-end">
477
+ <CopyButton
478
+ text={log.responseText}
479
+ label="Copy Response"
480
+ copied={responseCopy.copied}
481
+ onCopy={responseCopy.copy}
482
+ />
464
483
  </div>
465
- )}
466
- <div className="flex justify-end">
467
- <CopyButton
468
- text={log.responseText}
469
- label="Copy Response"
470
- copied={responseCopy.copied}
471
- onCopy={responseCopy.copy}
472
- />
484
+ {log.responseText !== null ? (
485
+ <JsonViewerFromString text={log.responseText} defaultExpandDepth={1} />
486
+ ) : (
487
+ <p className="text-xs text-muted-foreground italic">No response</p>
488
+ )}
489
+ {log.streaming === true && (
490
+ <StreamingChunkSequence
491
+ logId={log.id}
492
+ truncated={log.streamingChunksPath !== null}
493
+ />
494
+ )}
473
495
  </div>
474
- {log.responseText !== null ? (
475
- <JsonViewerFromString text={log.responseText} defaultExpandDepth={1} />
476
- ) : (
477
- <p className="text-xs text-muted-foreground italic">No response</p>
478
- )}
479
- {log.streaming === true && (
480
- <StreamingChunkSequence
481
- logId={log.id}
482
- truncated={log.streamingChunksPath !== null}
483
- />
484
- )}
485
- </div>
496
+ )}
486
497
  </TabsContent>
487
498
 
488
499
  <TabsContent value="parsed">
489
- <div className="px-4 py-3">
490
- <ResponseView
491
- responseText={log.responseText}
492
- responseStatus={log.responseStatus}
493
- streaming={log.streaming}
494
- inputTokens={log.inputTokens}
495
- outputTokens={log.outputTokens}
496
- cacheCreationInputTokens={log.cacheCreationInputTokens}
497
- cacheReadInputTokens={log.cacheReadInputTokens}
498
- apiFormat={resolvedFormat}
499
- error={log.error}
500
- />
501
- </div>
500
+ {activeTab === "parsed" && (
501
+ <div className="px-4 py-3">
502
+ <ResponseView
503
+ responseText={log.responseText}
504
+ responseStatus={log.responseStatus}
505
+ streaming={log.streaming}
506
+ inputTokens={log.inputTokens}
507
+ outputTokens={log.outputTokens}
508
+ cacheCreationInputTokens={log.cacheCreationInputTokens}
509
+ cacheReadInputTokens={log.cacheReadInputTokens}
510
+ apiFormat={resolvedFormat}
511
+ error={log.error}
512
+ />
513
+ </div>
514
+ )}
502
515
  </TabsContent>
503
516
  </Tabs>
504
517
  </div>