@tonyclaw/llm-inspector 1.15.0 → 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 (54) 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-BLYgekFx.js → main-BjnjXVBU.js} +1 -1
  6. package/.output/server/_libs/diff.mjs +2 -2
  7. package/.output/server/_ssr/{index-P66uoVEU.mjs → index-BIOEVAzU.mjs} +783 -588
  8. package/.output/server/_ssr/index.mjs +2 -2
  9. package/.output/server/_ssr/{router-DpLCKk51.mjs → router-THS9ptvu.mjs} +439 -177
  10. package/.output/server/{_tanstack-start-manifest_v-C9Wq6YdJ.mjs → _tanstack-start-manifest_v-BYhN7q_z.mjs} +1 -1
  11. package/.output/server/index.mjs +31 -31
  12. package/README.md +200 -113
  13. package/package.json +1 -1
  14. package/src/cli.ts +1 -0
  15. package/src/components/ProxyViewer.tsx +77 -85
  16. package/src/components/ProxyViewerContainer.tsx +148 -76
  17. package/src/components/providers/ImportWizardDialog.tsx +27 -3
  18. package/src/components/proxy-viewer/CompareDrawer.tsx +17 -4
  19. package/src/components/proxy-viewer/ConversationGroup.tsx +15 -47
  20. package/src/components/proxy-viewer/ConversationHeader.tsx +58 -5
  21. package/src/components/proxy-viewer/LogEntry.tsx +297 -329
  22. package/src/components/proxy-viewer/LogEntryHeader.tsx +126 -137
  23. package/src/components/proxy-viewer/ResponseView.tsx +14 -34
  24. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +3 -3
  25. package/src/components/proxy-viewer/TurnGroup.tsx +25 -21
  26. package/src/components/proxy-viewer/diff/DiffView.tsx +5 -3
  27. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -9
  28. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +3 -3
  29. package/src/components/proxy-viewer/formats/index.tsx +19 -10
  30. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +7 -3
  31. package/src/components/proxy-viewer/log-formats/anthropic.ts +48 -0
  32. package/src/components/proxy-viewer/log-formats/index.ts +23 -0
  33. package/src/components/proxy-viewer/log-formats/openai.ts +40 -0
  34. package/src/components/proxy-viewer/log-formats/types.ts +33 -0
  35. package/src/components/proxy-viewer/log-formats/unknown.ts +14 -0
  36. package/src/components/proxy-viewer/viewerState.ts +58 -0
  37. package/src/components/ui/json-viewer.tsx +3 -3
  38. package/src/lib/objectUtils.ts +22 -0
  39. package/src/proxy/claudeCodeStrip.ts +5 -8
  40. package/src/proxy/formats/index.ts +1 -1
  41. package/src/proxy/formats/registry.ts +9 -0
  42. package/src/proxy/handler.ts +2 -8
  43. package/src/proxy/logIndex.ts +58 -43
  44. package/src/proxy/logger.ts +51 -27
  45. package/src/proxy/openaiOrphanToolStrip.ts +11 -17
  46. package/src/proxy/providerImporters.ts +245 -19
  47. package/src/proxy/providers.ts +20 -7
  48. package/src/proxy/schemas.ts +5 -9
  49. package/src/proxy/socketTracker.ts +109 -78
  50. package/src/proxy/store.ts +68 -83
  51. package/src/routes/api/logs.ts +31 -2
  52. package/styles/globals.css +22 -0
  53. package/.output/public/assets/index-CMuJQyt1.js +0 -105
  54. package/.output/public/assets/index-DciyfYBk.css +0 -1
@@ -1,18 +1,18 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, a as parseRequest, R as RuntimeConfigSchema, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, O as OpenAIRequestSchema, p as parseOpenAIResponse, I as InspectorResponseSchema, s as stripClaudeCodeBillingHeader } from "./router-DpLCKk51.mjs";
2
+ import { C as CapturedLogSchema, R as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, b as createFailedProviderTestResults, d as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, a as AnthropicRequestSchema } from "./router-THS9ptvu.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
- import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
5
4
  import { c as clsx } from "../_libs/clsx.mjs";
6
5
  import { t as twMerge } from "../_libs/tailwind-merge.mjs";
7
6
  import { J as JSZip } from "../_libs/jszip.mjs";
8
7
  import { c as cva } from "../_libs/class-variance-authority.mjs";
9
- import { d as diffLines, a as diffJson } from "../_libs/diff.mjs";
10
8
  import { R as Root, T as Trigger$2, C as Content, a as Close, b as Title, P as Portal$2, O as Overlay } from "../_libs/radix-ui__react-dialog.mjs";
9
+ import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
+ import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
11
11
  import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
12
12
  import "../_libs/modelcontextprotocol__server.mjs";
13
- import { D as Download, S as Settings, C as ChevronDown, a as Check, X, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, R as Rows3, i as Columns2, j as Minus, k as Pencil, E as Equal, l as EyeOff, m as Eye, n as ExternalLink, o as RotateCw, T as Trash2, G as GitCompareArrows, p as RotateCcw, q as CircleCheckBig, W as Wrench, r as Globe, F as FileTerminal, s as Radio, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as TriangleAlert, H as CircleStop, I as ChevronsUp, J as ChevronsDown, K as Brain, N as Terminal } from "../_libs/lucide-react.mjs";
13
+ import { D as Download, S as Settings, C as ChevronDown, a as Check, X, U as Upload, b as Scan, P as Plus, c as Copy, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, T as Trash2, R as Rows3, i as Columns2, j as Minus, k as Pencil, E as Equal, l as EyeOff, m as Eye, n as ExternalLink, o as RotateCw, G as GitCompareArrows, p as RotateCcw, q as CircleCheckBig, W as Wrench, r as Globe, F as FileTerminal, s as Radio, t as CircleQuestionMark, u as Server, v as Gauge, w as Lock, x as Wifi, y as WifiOff, A as ArrowUp, z as ArrowDown, B as TriangleAlert, H as CircleStop, I as ChevronsUp, J as ChevronsDown, K as Brain, N as Terminal } from "../_libs/lucide-react.mjs";
14
14
  import { M as Markdown } from "../_libs/react-markdown.mjs";
15
- import { a as array, b as string, u as union, d as object, l as literal, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
15
+ import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
16
16
  import { P as Provider, R as Root3, T as Trigger$1, a as Portal$1, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
17
17
  import { R as Root2$1, L as List, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
18
18
  import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
@@ -36,8 +36,9 @@ import "stream";
36
36
  import "crypto";
37
37
  import "../_libs/isbot.mjs";
38
38
  import "node:fs";
39
- import "node:path";
40
39
  import "node:fs/promises";
40
+ import "node:buffer";
41
+ import "node:path";
41
42
  import "../_libs/conf.mjs";
42
43
  import "node:util";
43
44
  import "node:process";
@@ -62,7 +63,6 @@ import "../_libs/uint8array-extras.mjs";
62
63
  import "node:child_process";
63
64
  import "../_libs/use-sync-external-store.mjs";
64
65
  import "../_libs/dequal.mjs";
65
- import "../_libs/tanstack__virtual-core.mjs";
66
66
  import "../_libs/readable-stream.mjs";
67
67
  import "events";
68
68
  import "node:string_decoder";
@@ -99,6 +99,7 @@ import "../_libs/get-nonce.mjs";
99
99
  import "../_libs/use-sidecar.mjs";
100
100
  import "../_libs/use-callback-ref.mjs";
101
101
  import "../_libs/aria-hidden.mjs";
102
+ import "../_libs/tanstack__virtual-core.mjs";
102
103
  import "../_libs/radix-ui__number.mjs";
103
104
  import "../_libs/radix-ui__react-collection.mjs";
104
105
  import "../_libs/radix-ui__react-direction.mjs";
@@ -333,38 +334,10 @@ async function exportLogsAsZip(logs) {
333
334
  document.body.removeChild(anchor);
334
335
  URL.revokeObjectURL(url);
335
336
  }
336
- const version = "1.15.0";
337
+ const version = "1.16.0";
337
338
  const packageJson = {
338
339
  version
339
340
  };
340
- function isRecord(value) {
341
- return typeof value === "object" && value !== null && !Array.isArray(value);
342
- }
343
- function extractStopReason(log) {
344
- if (log.responseText === null) return null;
345
- try {
346
- let json = JSON.parse(log.responseText);
347
- if (typeof json === "string") {
348
- json = JSON.parse(json);
349
- }
350
- if (!isRecord(json)) return null;
351
- if (typeof json.stop_reason === "string") {
352
- if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
353
- return json.stop_reason;
354
- }
355
- return null;
356
- }
357
- if (Array.isArray(json.choices) && json.choices.length > 0 && isRecord(json.choices[0]) && typeof json.choices[0].finish_reason === "string" && json.choices[0].finish_reason === "stop") {
358
- return "stop";
359
- }
360
- return null;
361
- } catch {
362
- return null;
363
- }
364
- }
365
- function isTurnBoundary(stopReason) {
366
- return stopReason === "end_turn" || stopReason === "stop";
367
- }
368
341
  const badgeVariants = cva(
369
342
  "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
370
343
  {
@@ -400,6 +373,156 @@ function Badge({
400
373
  }
401
374
  );
402
375
  }
376
+ function Dialog({
377
+ ...props
378
+ }) {
379
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Root, { "data-slot": "dialog", ...props });
380
+ }
381
+ function DialogTrigger({
382
+ ...props
383
+ }) {
384
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { "data-slot": "dialog-trigger", ...props });
385
+ }
386
+ function DialogPortal({
387
+ ...props
388
+ }) {
389
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
390
+ }
391
+ function DialogOverlay({
392
+ className,
393
+ ...props
394
+ }) {
395
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
396
+ Overlay,
397
+ {
398
+ "data-slot": "dialog-overlay",
399
+ className: cn(
400
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
401
+ className
402
+ ),
403
+ ...props
404
+ }
405
+ );
406
+ }
407
+ function DialogContent({
408
+ className,
409
+ children,
410
+ ...props
411
+ }) {
412
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogPortal, { children: [
413
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogOverlay, {}),
414
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
415
+ Content,
416
+ {
417
+ "data-slot": "dialog-content",
418
+ className: cn(
419
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
420
+ className
421
+ ),
422
+ ...props,
423
+ children: [
424
+ children,
425
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [
426
+ /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" }),
427
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Close" })
428
+ ] })
429
+ ]
430
+ }
431
+ )
432
+ ] });
433
+ }
434
+ function DialogHeader({ className, ...props }) {
435
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
436
+ "div",
437
+ {
438
+ "data-slot": "dialog-header",
439
+ className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className),
440
+ ...props
441
+ }
442
+ );
443
+ }
444
+ function DialogTitle({
445
+ className,
446
+ ...props
447
+ }) {
448
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
449
+ Title,
450
+ {
451
+ "data-slot": "dialog-title",
452
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
453
+ ...props
454
+ }
455
+ );
456
+ }
457
+ const buttonVariants = cva(
458
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
459
+ {
460
+ variants: {
461
+ variant: {
462
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
463
+ destructive: "bg-destructive text-white hover:bg-destructive/90",
464
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
465
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
466
+ ghost: "hover:bg-accent hover:text-accent-foreground",
467
+ link: "text-primary underline-offset-4 hover:underline"
468
+ },
469
+ size: {
470
+ default: "h-9 px-4 py-2",
471
+ sm: "h-8 rounded-md px-3 text-xs",
472
+ lg: "h-10 rounded-md px-8",
473
+ icon: "size-9"
474
+ }
475
+ },
476
+ defaultVariants: {
477
+ variant: "default",
478
+ size: "default"
479
+ }
480
+ }
481
+ );
482
+ function Button({
483
+ className,
484
+ variant,
485
+ size,
486
+ ...props
487
+ }) {
488
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
489
+ "button",
490
+ {
491
+ "data-slot": "button",
492
+ className: cn(buttonVariants({ variant, size, className })),
493
+ ...props
494
+ }
495
+ );
496
+ }
497
+ function ConfirmDialog({
498
+ open,
499
+ onOpenChange,
500
+ title,
501
+ description,
502
+ confirmLabel = "Confirm",
503
+ variant = "default",
504
+ onConfirm
505
+ }) {
506
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
507
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: title }) }),
508
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: description }),
509
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
510
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
511
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
512
+ Button,
513
+ {
514
+ variant,
515
+ size: "sm",
516
+ onClick: () => {
517
+ onConfirm();
518
+ onOpenChange(false);
519
+ },
520
+ children: confirmLabel
521
+ }
522
+ )
523
+ ] })
524
+ ] }) });
525
+ }
403
526
  const API_FORMAT_LABELS = {
404
527
  anthropic: "Anthropic",
405
528
  openai: "OpenAI",
@@ -421,8 +544,15 @@ function ConversationHeader({
421
544
  onToggle,
422
545
  hideApiFormat = false,
423
546
  isLoading = false,
424
- userAgent
547
+ userAgent,
548
+ onClear
425
549
  }) {
550
+ const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
551
+ const handleClearClick = (e) => {
552
+ e.stopPropagation();
553
+ if (onClear === void 0) return;
554
+ setConfirmOpen(true);
555
+ };
426
556
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
427
557
  "div",
428
558
  {
@@ -432,10 +562,11 @@ function ConversationHeader({
432
562
  "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
433
563
  "hover:bg-muted/50",
434
564
  "select-none",
435
- "border border-border rounded-lg mb-2 bg-muted/30"
565
+ "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10"
436
566
  ),
437
567
  onClick: onToggle,
438
568
  onKeyDown: (e) => {
569
+ if (e.target !== e.currentTarget) return;
439
570
  if (e.key === "Enter" || e.key === " ") {
440
571
  e.preventDefault();
441
572
  onToggle();
@@ -499,7 +630,32 @@ function ConversationHeader({
499
630
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: formatTokens(totalOutputTokens) })
500
631
  ] })
501
632
  ] }),
502
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" })
633
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
634
+ onClear !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
635
+ "button",
636
+ {
637
+ type: "button",
638
+ onClick: handleClearClick,
639
+ "aria-label": `Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`,
640
+ title: "Clear this group",
641
+ className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-6 rounded hover:bg-muted cursor-pointer",
642
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" })
643
+ }
644
+ ),
645
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
646
+ ConfirmDialog,
647
+ {
648
+ open: confirmOpen,
649
+ onOpenChange: setConfirmOpen,
650
+ title: "Clear this group?",
651
+ description: `This will remove ${totalCalls} request${totalCalls !== 1 ? "s" : ""} from this conversation. This action cannot be undone.`,
652
+ confirmLabel: "Clear",
653
+ variant: "destructive",
654
+ onConfirm: () => {
655
+ onClear?.();
656
+ }
657
+ }
658
+ )
503
659
  ]
504
660
  }
505
661
  );
@@ -522,11 +678,15 @@ function hasMixedApiFormat(logs) {
522
678
  return false;
523
679
  }
524
680
  function getConversationId(log) {
525
- if (log.sessionId !== null && log.sessionId !== "") {
681
+ if (log.isTest === true) return "provider-test";
682
+ if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== void 0) {
526
683
  return log.sessionId;
527
684
  }
528
- const pid = log.clientPid !== null ? `PID:${log.clientPid}` : "unknown";
529
- const folder = log.clientProjectFolder !== null ? log.clientProjectFolder : "no-folder";
685
+ const hasPid = log.clientPid !== null && log.clientPid !== void 0;
686
+ const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== void 0;
687
+ if (!hasPid && !hasFolder) return "default";
688
+ const pid = hasPid ? `PID:${log.clientPid}` : "unknown";
689
+ const folder = hasFolder ? log.clientProjectFolder : "no-folder";
530
690
  return `${pid}|${folder}`;
531
691
  }
532
692
  function groupLogsByConversation(logs) {
@@ -552,45 +712,33 @@ function groupLogsByConversation(logs) {
552
712
  result.sort((a, b) => (a.logs[0]?.timestamp ?? "").localeCompare(b.logs[0]?.timestamp ?? ""));
553
713
  return result;
554
714
  }
555
- const buttonVariants = cva(
556
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
557
- {
558
- variants: {
559
- variant: {
560
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
561
- destructive: "bg-destructive text-white hover:bg-destructive/90",
562
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
563
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
564
- ghost: "hover:bg-accent hover:text-accent-foreground",
565
- link: "text-primary underline-offset-4 hover:underline"
566
- },
567
- size: {
568
- default: "h-9 px-4 py-2",
569
- sm: "h-8 rounded-md px-3 text-xs",
570
- lg: "h-10 rounded-md px-8",
571
- icon: "size-9"
715
+ function isRecord$1(value) {
716
+ return typeof value === "object" && value !== null && !Array.isArray(value);
717
+ }
718
+ function extractStopReason(log) {
719
+ if (log.responseText === null) return null;
720
+ try {
721
+ let json = JSON.parse(log.responseText);
722
+ if (typeof json === "string") {
723
+ json = JSON.parse(json);
724
+ }
725
+ if (!isRecord$1(json)) return null;
726
+ if (typeof json.stop_reason === "string") {
727
+ if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
728
+ return json.stop_reason;
572
729
  }
573
- },
574
- defaultVariants: {
575
- variant: "default",
576
- size: "default"
730
+ return null;
577
731
  }
578
- }
579
- );
580
- function Button({
581
- className,
582
- variant,
583
- size,
584
- ...props
585
- }) {
586
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
587
- "button",
588
- {
589
- "data-slot": "button",
590
- className: cn(buttonVariants({ variant, size, className })),
591
- ...props
732
+ if (Array.isArray(json.choices) && json.choices.length > 0 && isRecord$1(json.choices[0]) && typeof json.choices[0].finish_reason === "string" && json.choices[0].finish_reason === "stop") {
733
+ return "stop";
592
734
  }
593
- );
735
+ return null;
736
+ } catch {
737
+ return null;
738
+ }
739
+ }
740
+ function isTurnBoundary(stopReason) {
741
+ return stopReason === "end_turn" || stopReason === "stop";
594
742
  }
595
743
  function classifyValue(value) {
596
744
  if (value === null) return "null";
@@ -779,7 +927,7 @@ function ExpandCollapseButton({
779
927
  }
780
928
  );
781
929
  }
782
- function JsonNode({
930
+ const JsonNode = reactExports.memo(function JsonNode2({
783
931
  name,
784
932
  value,
785
933
  level,
@@ -853,7 +1001,7 @@ function JsonNode({
853
1001
  ),
854
1002
  expandable && expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pl-4", children: [
855
1003
  getEntries(value).map(([key, childValue]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
856
- JsonNode,
1004
+ JsonNode2,
857
1005
  {
858
1006
  name: key,
859
1007
  value: childValue,
@@ -866,7 +1014,7 @@ function JsonNode({
866
1014
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground py-0.5 px-1", children: closeBracket })
867
1015
  ] }, childResetKey)
868
1016
  ] });
869
- }
1017
+ });
870
1018
  function JsonViewer({
871
1019
  data,
872
1020
  defaultExpandDepth = 2,
@@ -1112,7 +1260,7 @@ function kindClass(kind) {
1112
1260
  }
1113
1261
  return "text-foreground/80";
1114
1262
  }
1115
- function DiffView({ result, emptyLabel }) {
1263
+ const DiffViewInner = function DiffView2({ result, emptyLabel }) {
1116
1264
  const [mode, setMode] = reactExports.useState("unified");
1117
1265
  const scrollRef = reactExports.useRef(null);
1118
1266
  const virtualizer = useVirtualizer({
@@ -1189,7 +1337,7 @@ function DiffView({ result, emptyLabel }) {
1189
1337
  }
1190
1338
  )
1191
1339
  ] });
1192
- }
1340
+ };
1193
1341
  function UnifiedRows({
1194
1342
  virtualizer,
1195
1343
  lines
@@ -1347,6 +1495,7 @@ function SplitRows({ lines }) {
1347
1495
  idx
1348
1496
  )) });
1349
1497
  }
1498
+ const DiffView = reactExports.memo(DiffViewInner);
1350
1499
  const AnthropicLogoSvg = "data:image/svg+xml,%3csvg%20height='2500'%20viewBox='0%206.603%201192.672%201193.397'%20width='2500'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='m233.96%20800.215%20234.684-131.678%203.947-11.436-3.947-6.363h-11.436l-39.221-2.416-134.094-3.624-116.296-4.832-112.67-6.04-28.35-6.04-26.577-35.035%202.738-17.477%2023.84-16.027%2034.147%202.98%2075.463%205.155%20113.235%207.812%2082.147%204.832%20121.692%2012.644h19.329l2.738-7.812-6.604-4.832-5.154-4.832-117.182-79.41-126.845-83.92-66.443-48.321-35.92-24.484-18.12-22.953-7.813-50.093%2032.618-35.92%2043.812%202.98%2011.195%202.98%2044.375%2034.147%2094.792%2073.37%20123.786%2091.167%2018.12%2015.06%207.249-5.154.886-3.624-8.135-13.61-67.329-121.692-71.838-123.785-31.974-51.302-8.456-30.765c-2.98-12.645-5.154-23.275-5.154-36.242l37.127-50.416%2020.537-6.604%2049.53%206.604%2020.86%2018.121%2030.765%2070.39%2049.852%20110.818%2077.315%20150.684%2022.631%2044.698%2012.08%2041.396%204.51%2012.645h7.813v-7.248l6.362-84.886%2011.759-104.215%2011.436-134.094%203.946-37.772%2018.685-45.262%2037.127-24.482%2028.994%2013.852%2023.839%2034.148-3.303%2022.067-14.174%2092.134-27.785%20144.323-18.121%2096.644h10.55l12.08-12.08%2048.887-64.913%2082.147-102.685%2036.242-40.752%2042.282-45.02%2027.14-21.423h51.303l37.772%2056.135-16.913%2057.986-52.832%2067.007-43.812%2056.779-62.82%2084.563-39.22%2067.651%203.623%205.396%209.343-.886%20141.906-30.201%2076.671-13.852%2091.49-15.705%2041.396%2019.329%204.51%2019.65-16.269%2040.189-97.852%2024.16-114.764%2022.954-170.9%2040.43-2.093%201.53%202.416%202.98%2076.993%207.248%2032.94%201.771h80.617l150.12%2011.195%2039.222%2025.933%2023.517%2031.732-3.946%2024.16-60.403%2030.766-81.503-19.33-190.228-45.26-65.235-16.27h-9.02v5.397l54.362%2053.154%2099.624%2089.96%20124.752%20115.973%206.362%2028.671-16.027%2022.63-16.912-2.415-109.611-82.47-42.282-37.127-95.758-80.618h-6.363v8.456l22.067%2032.296%20116.537%20175.167%206.04%2053.719-8.456%2017.476-30.201%2010.55-33.181-6.04-68.215-95.758-70.39-107.84-56.778-96.644-6.926%203.947-33.503%20360.886-15.705%2018.443-36.243%2013.852-30.201-22.953-16.027-37.127%2016.027-73.37%2019.329-95.758%2015.704-76.107%2014.175-94.55%208.456-31.41-.563-2.094-6.927.886-71.275%2097.852-108.402%20146.497-85.772%2091.812-20.537%208.134-35.597-18.443%203.301-32.94%2019.893-29.315%20118.712-151.007%2071.597-93.583%2046.228-54.04-.322-7.813h-2.738l-315.302%20204.725-56.135%207.248-24.16-22.63%202.98-37.128%2011.435-12.08%2094.792-65.236-.322.323z'%20fill='%23d97757'/%3e%3c/svg%3e";
1351
1500
  const OpenAILogoSvg = "data:image/svg+xml,%3csvg%20height='2500'%20viewBox='-1%20-.1%20949.1%20959.8'%20width='2474'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='m925.8%20456.3c10.4%2023.2%2017%2048%2019.7%2073.3%202.6%2025.3%201.3%2050.9-4.1%2075.8-5.3%2024.9-14.5%2048.8-27.3%2070.8-8.4%2014.7-18.3%2028.5-29.7%2041.2-11.3%2012.6-23.9%2024-37.6%2034-13.8%2010-28.5%2018.4-44.1%2025.3-15.5%206.8-31.7%2012-48.3%2015.4-7.8%2024.2-19.4%2047.1-34.4%2067.7-14.9%2020.6-33%2038.7-53.6%2053.6-20.6%2015-43.4%2026.6-67.6%2034.4-24.2%207.9-49.5%2011.8-75%2011.8-16.9.1-33.9-1.7-50.5-5.1-16.5-3.5-32.7-8.8-48.2-15.7s-30.2-15.5-43.9-25.5c-13.6-10-26.2-21.5-37.4-34.2-25%205.4-50.6%206.7-75.9%204.1-25.3-2.7-50.1-9.3-73.4-19.7-23.2-10.3-44.7-24.3-63.6-41.4s-35-37.1-47.7-59.1c-8.5-14.7-15.5-30.2-20.8-46.3s-8.8-32.7-10.6-49.6c-1.8-16.8-1.7-33.8.1-50.7%201.8-16.8%205.5-33.4%2010.8-49.5-17-18.9-31-40.4-41.4-63.6-10.3-23.3-17-48-19.6-73.3-2.7-25.3-1.3-50.9%204-75.8s14.5-48.8%2027.3-70.8c8.4-14.7%2018.3-28.6%2029.6-41.2s24-24%2037.7-34%2028.5-18.5%2044-25.3c15.6-6.9%2031.8-12%2048.4-15.4%207.8-24.3%2019.4-47.1%2034.3-67.7%2015-20.6%2033.1-38.7%2053.7-53.7%2020.6-14.9%2043.4-26.5%2067.6-34.4%2024.2-7.8%2049.5-11.8%2075-11.7%2016.9-.1%2033.9%201.6%2050.5%205.1s32.8%208.7%2048.3%2015.6c15.5%207%2030.2%2015.5%2043.9%2025.5%2013.7%2010.1%2026.3%2021.5%2037.5%2034.2%2024.9-5.3%2050.5-6.6%2075.8-4s50%209.3%2073.3%2019.6c23.2%2010.4%2044.7%2024.3%2063.6%2041.4%2018.9%2017%2035%2036.9%2047.7%2059%208.5%2014.6%2015.5%2030.1%2020.8%2046.3%205.3%2016.1%208.9%2032.7%2010.6%2049.6%201.8%2016.9%201.8%2033.9-.1%2050.8-1.8%2016.9-5.5%2033.5-10.8%2049.6%2017.1%2018.9%2031%2040.3%2041.4%2063.6zm-333.2%20426.9c21.8-9%2041.6-22.3%2058.3-39s30-36.5%2039-58.4c9-21.8%2013.7-45.2%2013.7-68.8v-223q-.1-.3-.2-.7-.1-.3-.3-.6-.2-.3-.5-.5-.3-.3-.6-.4l-80.7-46.6v269.4c0%202.7-.4%205.5-1.1%208.1-.7%202.7-1.7%205.2-3.1%207.6s-3%204.6-5%206.5a32.1%2032.1%200%200%201%20-6.5%205l-191.1%20110.3c-1.6%201-4.3%202.4-5.7%203.2%207.9%206.7%2016.5%2012.6%2025.5%2017.8%209.1%205.2%2018.5%209.6%2028.3%2013.2%209.8%203.5%2019.9%206.2%2030.1%208%2010.3%201.8%2020.7%202.7%2031.1%202.7%2023.6%200%2047-4.7%2068.8-13.8zm-455.1-151.4c11.9%2020.5%2027.6%2038.3%2046.3%2052.7%2018.8%2014.4%2040.1%2024.9%2062.9%2031s46.6%207.7%2070%204.6%2045.9-10.7%2066.4-22.5l193.2-111.5.5-.5q.2-.2.3-.6.2-.3.3-.6v-94l-233.2%20134.9c-2.4%201.4-4.9%202.4-7.5%203.2-2.7.7-5.4%201-8.2%201-2.7%200-5.4-.3-8.1-1-2.6-.8-5.2-1.8-7.6-3.2l-191.1-110.4c-1.7-1-4.2-2.5-5.6-3.4-1.8%2010.3-2.7%2020.7-2.7%2031.1s1%2020.8%202.8%2031.1c1.8%2010.2%204.6%2020.3%208.1%2030.1%203.6%209.8%208%2019.2%2013.2%2028.2zm-50.2-417c-11.8%2020.5-19.4%2043.1-22.5%2066.5s-1.5%2047.1%204.6%2070c6.1%2022.8%2016.6%2044.1%2031%2062.9%2014.4%2018.7%2032.3%2034.4%2052.7%2046.2l193.1%20111.6q.3.1.7.2h.7q.4%200%20.7-.2.3-.1.6-.3l81-46.8-233.2-134.6c-2.3-1.4-4.5-3.1-6.5-5a32.1%2032.1%200%200%201%20-5-6.5c-1.3-2.4-2.4-4.9-3.1-7.6-.7-2.6-1.1-5.3-1-8.1v-227.1c-9.8%203.6-19.3%208-28.3%2013.2-9%205.3-17.5%2011.3-25.5%2018-7.9%206.7-15.3%2014.1-22%2022.1-6.7%207.9-12.6%2016.5-17.8%2025.5zm663.3%20154.4c2.4%201.4%204.6%203%206.6%205%201.9%201.9%203.6%204.1%205%206.5%201.3%202.4%202.4%205%203.1%207.6.6%202.7%201%205.4.9%208.2v227.1c32.1-11.8%2060.1-32.5%2080.8-59.7%2020.8-27.2%2033.3-59.7%2036.2-93.7s-3.9-68.2-19.7-98.5-39.9-55.5-69.5-72.5l-193.1-111.6q-.3-.1-.7-.2h-.7q-.3.1-.7.2-.3.1-.6.3l-80.6%2046.6%20233.2%20134.7zm80.5-121h-.1v.1zm-.1-.1c5.8-33.6%201.9-68.2-11.3-99.7-13.1-31.5-35-58.6-63-78.2-28-19.5-61-30.7-95.1-32.2-34.2-1.4-68%206.9-97.6%2023.9l-193.1%20111.5q-.3.2-.5.5l-.4.6q-.1.3-.2.7-.1.3-.1.7v93.2l233.2-134.7c2.4-1.4%205-2.4%207.6-3.2%202.7-.7%205.4-1%208.1-1%202.8%200%205.5.3%208.2%201%202.6.8%205.1%201.8%207.5%203.2l191.1%20110.4c1.7%201%204.2%202.4%205.6%203.3zm-505.3-103.2c0-2.7.4-5.4%201.1-8.1.7-2.6%201.7-5.2%203.1-7.6%201.4-2.3%203-4.5%205-6.5%201.9-1.9%204.1-3.6%206.5-4.9l191.1-110.3c1.8-1.1%204.3-2.5%205.7-3.2-26.2-21.9-58.2-35.9-92.1-40.2-33.9-4.4-68.3%201-99.2%2015.5-31%2014.5-57.2%2037.6-75.5%2066.4-18.3%2028.9-28%2062.3-28%2096.5v223q.1.4.2.7.1.3.3.6.2.3.5.6.2.2.6.4l80.7%2046.6zm43.8%20294.7%20103.9%2060%20103.9-60v-119.9l-103.8-60-103.9%2060z'/%3e%3c/svg%3e";
1352
1501
  const DeepSeekLogoSvg = "data:image/svg+xml,%3csvg%20fill='none'%20height='1320'%20viewBox='3.771%206.973%2023.993%2017.652'%20width='2500'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='m27.501%208.469c-.252-.123-.36.111-.508.23-.05.04-.093.09-.135.135-.368.395-.797.652-1.358.621-.821-.045-1.521.213-2.14.842-.132-.776-.57-1.238-1.235-1.535-.349-.155-.701-.309-.944-.645-.171-.238-.217-.504-.303-.765-.054-.159-.108-.32-.29-.348-.197-.031-.274.135-.352.273-.31.567-.43%201.192-.419%201.825.028%201.421.628%202.554%201.82%203.36.136.093.17.186.128.321-.081.278-.178.547-.264.824-.054.178-.135.217-.324.14a5.448%205.448%200%200%201%20-1.719-1.169c-.848-.82-1.614-1.726-2.57-2.435-.225-.166-.449-.32-.681-.467-.976-.95.128-1.729.383-1.82.267-.096.093-.428-.77-.424s-1.653.293-2.659.677a2.782%202.782%200%200%201%20-.46.135%209.554%209.554%200%200%200%20-2.853-.1c-1.866.21-3.356%201.092-4.452%202.6-1.315%201.81-1.625%203.87-1.246%206.018.399%202.261%201.552%204.136%203.326%205.601%201.837%201.518%203.955%202.262%206.37%202.12%201.466-.085%203.1-.282%204.942-1.842.465.23.952.322%201.762.392.623.059%201.223-.031%201.687-.127.728-.154.677-.828.414-.953-2.132-.994-1.665-.59-2.09-.916%201.084-1.285%202.717-2.619%203.356-6.94.05-.343.007-.558%200-.837-.004-.168.034-.235.228-.254a4.084%204.084%200%200%200%201.529-.47c1.382-.757%201.938-1.997%202.07-3.485.02-.227-.004-.463-.243-.582zm-12.041%2013.391c-2.067-1.627-3.07-2.162-3.483-2.138-.387.021-.318.465-.233.754.089.285.205.482.368.732.113.166.19.414-.112.598-.666.414-1.823-.139-1.878-.166-1.347-.793-2.473-1.842-3.267-3.276-.765-1.38-1.21-2.861-1.284-4.441-.02-.383.093-.518.472-.586a4.692%204.692%200%200%201%201.514-.04c2.109.31%203.905%201.255%205.41%202.749.86.853%201.51%201.871%202.18%202.865.711%201.057%201.478%202.063%202.454%202.887.343.289.619.51.881.672-.792.088-2.117.107-3.022-.61zm.99-6.38a.304.304%200%201%201%20.609%200c0%20.17-.136.304-.306.304a.3.3%200%200%201%20-.303-.305zm3.077%201.581c-.197.08-.394.15-.584.159a1.246%201.246%200%200%201%20-.79-.252c-.27-.227-.463-.354-.546-.752a1.752%201.752%200%200%201%20.016-.582c.07-.324-.008-.531-.235-.72-.187-.155-.422-.196-.682-.196a.551.551%200%200%201%20-.252-.078c-.108-.055-.197-.19-.112-.356.027-.053.159-.183.19-.207.352-.201.758-.135%201.134.016.349.142.611.404.99.773.388.448.457.573.678.906.174.264.333.534.441.842.066.192-.02.35-.248.448z'%20fill='%234d6bfe'/%3e%3c/svg%3e";
@@ -1461,13 +1610,14 @@ const LogEntryHeader = reactExports.memo(function({
1461
1610
  }) {
1462
1611
  const statusCategory = getStatusCategory(log.responseStatus);
1463
1612
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1464
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1613
+ const toolNamesJoined = reactExports.useMemo(() => responseToolNames?.join(", ") ?? null, [responseToolNames]);
1614
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1465
1615
  "div",
1466
1616
  {
1467
1617
  role: "button",
1468
1618
  tabIndex: 0,
1469
1619
  className: cn(
1470
- "flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors",
1620
+ "flex items-center gap-2 px-3 py-1 cursor-pointer transition-colors",
1471
1621
  "hover:bg-muted/50",
1472
1622
  "select-none"
1473
1623
  ),
@@ -1483,15 +1633,15 @@ const LogEntryHeader = reactExports.memo(function({
1483
1633
  "#",
1484
1634
  log.id
1485
1635
  ] }),
1486
- log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1636
+ log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1487
1637
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
1488
1638
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.model })
1489
- ] }) }),
1639
+ ] }),
1490
1640
  statusCategory !== "success" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: statusCategory === "server_error" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1491
1641
  Badge,
1492
1642
  {
1493
1643
  variant: "destructive",
1494
- className: "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
1644
+ className: "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
1495
1645
  children: log.responseStatus
1496
1646
  }
1497
1647
  ) : statusCategory === "pending" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -1499,7 +1649,7 @@ const LogEntryHeader = reactExports.memo(function({
1499
1649
  {
1500
1650
  variant: "outline",
1501
1651
  className: cn(
1502
- "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
1652
+ "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
1503
1653
  STATUS_BADGE_CLASSES[statusCategory]
1504
1654
  ),
1505
1655
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3 animate-spin" })
@@ -1509,7 +1659,7 @@ const LogEntryHeader = reactExports.memo(function({
1509
1659
  {
1510
1660
  variant: "outline",
1511
1661
  className: cn(
1512
- "text-[10px] px-1.5 py-0 h-5 font-mono tabular-nums",
1662
+ "text-[10px] px-1.5 py-0 h-4 font-mono tabular-nums",
1513
1663
  STATUS_BADGE_CLASSES[statusCategory]
1514
1664
  ),
1515
1665
  children: log.responseStatus
@@ -1522,10 +1672,16 @@ const LogEntryHeader = reactExports.memo(function({
1522
1672
  hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1523
1673
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
1524
1674
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
1525
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground", children: [
1526
- "IN ",
1527
- log.inputTokens !== null ? formatTokens(log.inputTokens) : "—"
1528
- ] }),
1675
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
1676
+ "span",
1677
+ {
1678
+ className: log.inputTokens !== null ? "text-blue-400" : "text-muted-foreground",
1679
+ children: [
1680
+ "IN ",
1681
+ log.inputTokens !== null ? formatTokens(log.inputTokens) : "—"
1682
+ ]
1683
+ }
1684
+ ),
1529
1685
  " / ",
1530
1686
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
1531
1687
  "span",
@@ -1539,7 +1695,7 @@ const LogEntryHeader = reactExports.memo(function({
1539
1695
  )
1540
1696
  ] })
1541
1697
  ] }),
1542
- log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1698
+ log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1543
1699
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1544
1700
  /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1545
1701
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
@@ -1548,8 +1704,8 @@ const LogEntryHeader = reactExports.memo(function({
1548
1704
  ] })
1549
1705
  ] }) }),
1550
1706
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
1551
- ] }) }),
1552
- log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1707
+ ] }),
1708
+ log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1553
1709
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1554
1710
  /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1555
1711
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
@@ -1558,31 +1714,31 @@ const LogEntryHeader = reactExports.memo(function({
1558
1714
  ] })
1559
1715
  ] }) }),
1560
1716
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
1561
- ] }) }),
1562
- messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1717
+ ] }),
1718
+ messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1563
1719
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1564
1720
  /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
1565
1721
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: messageCount })
1566
1722
  ] }) }),
1567
1723
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of messages in the conversation" })
1568
- ] }) }),
1569
- toolCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1724
+ ] }),
1725
+ toolCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1570
1726
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1571
1727
  /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1572
1728
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1573
1729
  ] }) }),
1574
1730
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Number of tools defined in the request" })
1575
- ] }) }),
1576
- responseToolNames !== null && responseToolNames.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1731
+ ] }),
1732
+ responseToolNames !== null && responseToolNames.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1577
1733
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-amber-400/80 text-xs shrink-0", children: [
1578
1734
  /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1579
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[160px]", children: responseToolNames.join(", ") })
1735
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[160px]", children: toolNamesJoined })
1580
1736
  ] }) }),
1581
1737
  /* @__PURE__ */ jsxRuntimeExports.jsxs(TooltipContent, { children: [
1582
1738
  "Tools called by model: ",
1583
- responseToolNames.join(", ")
1739
+ toolNamesJoined
1584
1740
  ] })
1585
- ] }) }),
1741
+ ] }),
1586
1742
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1587
1743
  "span",
1588
1744
  {
@@ -1594,7 +1750,7 @@ const LogEntryHeader = reactExports.memo(function({
1594
1750
  ]
1595
1751
  }
1596
1752
  ),
1597
- (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1753
+ (log.clientPid !== null || log.clientProjectFolder !== null) && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1598
1754
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "hidden xl:flex items-center gap-1 text-purple-400/80 text-xs shrink-0", children: [
1599
1755
  /* @__PURE__ */ jsxRuntimeExports.jsx(FileTerminal, { className: "size-3" }),
1600
1756
  log.clientProjectFolder !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: log.clientProjectFolder }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
@@ -1603,98 +1759,17 @@ const LogEntryHeader = reactExports.memo(function({
1603
1759
  ] })
1604
1760
  ] }) }),
1605
1761
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: log.clientCwd !== null ? `PID: ${log.clientPid ?? "?"} CWD: ${log.clientCwd}` : `Process ID: ${log.clientPid ?? "?"}` })
1606
- ] }) }),
1607
- log.streaming && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1762
+ ] }),
1763
+ log.streaming && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1608
1764
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Radio, { className: "size-3 text-muted-foreground/60 shrink-0" }) }),
1609
1765
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request used SSE streaming" })
1610
- ] }) }),
1766
+ ] }),
1611
1767
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
1612
1768
  expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" })
1613
1769
  ]
1614
1770
  }
1615
- );
1771
+ ) });
1616
1772
  });
1617
- function Dialog({
1618
- ...props
1619
- }) {
1620
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Root, { "data-slot": "dialog", ...props });
1621
- }
1622
- function DialogTrigger({
1623
- ...props
1624
- }) {
1625
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { "data-slot": "dialog-trigger", ...props });
1626
- }
1627
- function DialogPortal({
1628
- ...props
1629
- }) {
1630
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$2, { "data-slot": "dialog-portal", ...props });
1631
- }
1632
- function DialogOverlay({
1633
- className,
1634
- ...props
1635
- }) {
1636
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1637
- Overlay,
1638
- {
1639
- "data-slot": "dialog-overlay",
1640
- className: cn(
1641
- "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1642
- className
1643
- ),
1644
- ...props
1645
- }
1646
- );
1647
- }
1648
- function DialogContent({
1649
- className,
1650
- children,
1651
- ...props
1652
- }) {
1653
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogPortal, { children: [
1654
- /* @__PURE__ */ jsxRuntimeExports.jsx(DialogOverlay, {}),
1655
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1656
- Content,
1657
- {
1658
- "data-slot": "dialog-content",
1659
- className: cn(
1660
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
1661
- className
1662
- ),
1663
- ...props,
1664
- children: [
1665
- children,
1666
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [
1667
- /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-4" }),
1668
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "sr-only", children: "Close" })
1669
- ] })
1670
- ]
1671
- }
1672
- )
1673
- ] });
1674
- }
1675
- function DialogHeader({ className, ...props }) {
1676
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1677
- "div",
1678
- {
1679
- "data-slot": "dialog-header",
1680
- className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className),
1681
- ...props
1682
- }
1683
- );
1684
- }
1685
- function DialogTitle({
1686
- className,
1687
- ...props
1688
- }) {
1689
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1690
- Title,
1691
- {
1692
- "data-slot": "dialog-title",
1693
- className: cn("text-lg font-semibold leading-none tracking-tight", className),
1694
- ...props
1695
- }
1696
- );
1697
- }
1698
1773
  function Separator({
1699
1774
  className,
1700
1775
  orientation = "horizontal",
@@ -1807,7 +1882,7 @@ function extractThinkingFromContent(text) {
1807
1882
  const remainingText = text.replace(THINKING_TAG_REGEX, "").trim();
1808
1883
  return { thinking, remainingText };
1809
1884
  }
1810
- function TextBlock({ text }) {
1885
+ const TextBlock = reactExports.memo(function TextBlock2({ text }) {
1811
1886
  if (text.includes("<system-reminder>")) {
1812
1887
  return /* @__PURE__ */ jsxRuntimeExports.jsx(SystemReminderBlock, { text });
1813
1888
  }
@@ -1817,8 +1892,10 @@ function TextBlock({ text }) {
1817
1892
  remainingText.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: remainingText }) }),
1818
1893
  thinking === null && remainingText.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty text block" })
1819
1894
  ] });
1820
- }
1821
- function ThinkingBlock({ thinking }) {
1895
+ });
1896
+ const ThinkingBlock = reactExports.memo(function ThinkingBlock2({
1897
+ thinking
1898
+ }) {
1822
1899
  const [open, setOpen] = reactExports.useState(false);
1823
1900
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-l-2 border-purple-500/40 my-1", children: [
1824
1901
  /* @__PURE__ */ jsxRuntimeExports.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-purple-500/5 transition-colors rounded-r-sm group", children: [
@@ -1840,8 +1917,8 @@ function ThinkingBlock({ thinking }) {
1840
1917
  ] }),
1841
1918
  /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-xs text-muted-foreground whitespace-pre-wrap font-mono leading-relaxed", children: thinking }) }) }) })
1842
1919
  ] }) });
1843
- }
1844
- function ToolUseBlock({
1920
+ });
1921
+ const ToolUseBlock = reactExports.memo(function ToolUseBlock2({
1845
1922
  name,
1846
1923
  input
1847
1924
  }) {
@@ -1855,8 +1932,8 @@ function ToolUseBlock({
1855
1932
  ] }),
1856
1933
  /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-[60vh]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(input), defaultExpandDepth: 2 }) }) }) })
1857
1934
  ] }) });
1858
- }
1859
- function ResponseContentBlockRenderer({
1935
+ });
1936
+ const ResponseContentBlockRenderer = reactExports.memo(function ResponseContentBlockRenderer2({
1860
1937
  block
1861
1938
  }) {
1862
1939
  switch (block.type) {
@@ -1870,8 +1947,8 @@ function ResponseContentBlockRenderer({
1870
1947
  default:
1871
1948
  return assertNever();
1872
1949
  }
1873
- }
1874
- function StructuredResponseViewAnthropic({
1950
+ });
1951
+ const StructuredResponseViewAnthropic = reactExports.memo(function StructuredResponseViewAnthropic2({
1875
1952
  response
1876
1953
  }) {
1877
1954
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
@@ -1913,7 +1990,7 @@ function StructuredResponseViewAnthropic({
1913
1990
  response.content.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
1914
1991
  ] })
1915
1992
  ] });
1916
- }
1993
+ });
1917
1994
  function parseToolArguments(raw) {
1918
1995
  if (raw === void 0 || raw === "") return {};
1919
1996
  try {
@@ -1941,7 +2018,9 @@ function OpenAIToolCallBlock({ call }) {
1941
2018
  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(parsed), defaultExpandDepth: 2 }) }) }) })
1942
2019
  ] }) });
1943
2020
  }
1944
- function OpenAIResponseView({ response }) {
2021
+ const OpenAIResponseView = reactExports.memo(function OpenAIResponseView2({
2022
+ response
2023
+ }) {
1945
2024
  const choice = response.choices[0];
1946
2025
  const message = choice?.message;
1947
2026
  const toolCalls = message?.tool_calls ?? [];
@@ -1999,17 +2078,119 @@ function OpenAIResponseView({ response }) {
1999
2078
  (message?.content === null || message?.content === void 0 || message.content.length === 0) && (message?.reasoning_content === null || message?.reasoning_content === void 0 || message.reasoning_content.length === 0) && (message?.function_call === null || message?.function_call === void 0) && toolCalls.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "Empty response content" })
2000
2079
  ] })
2001
2080
  ] });
2081
+ });
2082
+ function isRecord(value) {
2083
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2002
2084
  }
2003
- function isOpenAIResponse(resp) {
2004
- if (typeof resp !== "object" || resp === null) return false;
2005
- const desc = Object.getOwnPropertyDescriptor(resp, "object");
2006
- return desc?.value === "chat.completion";
2085
+ function isOpenAIResponse(response) {
2086
+ return isRecord(response) && response.object === "chat.completion";
2087
+ }
2088
+ function isAnthropicResponse(response) {
2089
+ return isRecord(response) && response.type === "message" && Array.isArray(response.content);
2007
2090
  }
2008
2091
  function formatViewFor(apiFormat, response) {
2009
- if (isOpenAIResponse(response)) {
2092
+ if (apiFormat === "openai" && isOpenAIResponse(response)) {
2010
2093
  return /* @__PURE__ */ jsxRuntimeExports.jsx(OpenAIResponseView, { response });
2011
2094
  }
2012
- return /* @__PURE__ */ jsxRuntimeExports.jsx(StructuredResponseViewAnthropic, { response });
2095
+ if (apiFormat === "anthropic" && isAnthropicResponse(response)) {
2096
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(StructuredResponseViewAnthropic, { response });
2097
+ }
2098
+ return null;
2099
+ }
2100
+ function emptyRequestAnalysis(rawBody) {
2101
+ return {
2102
+ parsed: null,
2103
+ comparisonValue: rawBody,
2104
+ messageCount: null,
2105
+ toolCount: null
2106
+ };
2107
+ }
2108
+ const EMPTY_RESPONSE_ANALYSIS = {
2109
+ parsed: null,
2110
+ toolNames: null
2111
+ };
2112
+ const anthropicLogFormatAdapter = {
2113
+ format: "anthropic",
2114
+ analyzeRequest(rawBody) {
2115
+ if (rawBody === null) return emptyRequestAnalysis(rawBody);
2116
+ try {
2117
+ const result = AnthropicRequestSchema.safeParse(JSON.parse(rawBody));
2118
+ if (!result.success) return emptyRequestAnalysis(rawBody);
2119
+ return {
2120
+ parsed: result.data,
2121
+ comparisonValue: result.data,
2122
+ messageCount: result.data.messages.length,
2123
+ toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
2124
+ };
2125
+ } catch {
2126
+ return emptyRequestAnalysis(rawBody);
2127
+ }
2128
+ },
2129
+ analyzeResponse(responseText) {
2130
+ if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
2131
+ try {
2132
+ let json = JSON.parse(responseText);
2133
+ if (typeof json === "string") json = JSON.parse(json);
2134
+ const result = AnthropicResponseSchema$1.safeParse(json);
2135
+ if (!result.success) return EMPTY_RESPONSE_ANALYSIS;
2136
+ const toolNames = result.data.content.filter((block) => block.type === "tool_use").map((block) => block.name);
2137
+ return {
2138
+ parsed: result.data,
2139
+ toolNames: toolNames.length > 0 ? toolNames : null
2140
+ };
2141
+ } catch {
2142
+ return EMPTY_RESPONSE_ANALYSIS;
2143
+ }
2144
+ }
2145
+ };
2146
+ const openAILogFormatAdapter = {
2147
+ format: "openai",
2148
+ analyzeRequest(rawBody) {
2149
+ if (rawBody === null) return emptyRequestAnalysis(rawBody);
2150
+ try {
2151
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(rawBody));
2152
+ if (!result.success) return emptyRequestAnalysis(rawBody);
2153
+ return {
2154
+ parsed: result.data,
2155
+ comparisonValue: result.data,
2156
+ messageCount: result.data.messages.length,
2157
+ toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
2158
+ };
2159
+ } catch {
2160
+ return emptyRequestAnalysis(rawBody);
2161
+ }
2162
+ },
2163
+ analyzeResponse(responseText) {
2164
+ if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
2165
+ const parsed = parseOpenAIResponse(responseText);
2166
+ if (parsed === null) return EMPTY_RESPONSE_ANALYSIS;
2167
+ const toolNames = parsed.choices[0]?.message?.tool_calls?.map((toolCall) => toolCall.function?.name ?? "").filter((name) => name !== "") ?? [];
2168
+ return {
2169
+ parsed,
2170
+ toolNames: toolNames.length > 0 ? toolNames : null
2171
+ };
2172
+ }
2173
+ };
2174
+ const unknownLogFormatAdapter = {
2175
+ format: "unknown",
2176
+ analyzeRequest(rawBody) {
2177
+ return emptyRequestAnalysis(rawBody);
2178
+ },
2179
+ analyzeResponse() {
2180
+ return EMPTY_RESPONSE_ANALYSIS;
2181
+ }
2182
+ };
2183
+ const ADAPTERS = {
2184
+ anthropic: anthropicLogFormatAdapter,
2185
+ openai: openAILogFormatAdapter,
2186
+ unknown: unknownLogFormatAdapter
2187
+ };
2188
+ function getLogFormatAdapter(format) {
2189
+ return ADAPTERS[format];
2190
+ }
2191
+ function resolveLogFormat(log) {
2192
+ const pathFormat = requestFormatForPath(log.path);
2193
+ return pathFormat === "unknown" ? log.apiFormat : pathFormat;
2013
2194
  }
2014
2195
  function getStatusClasses(category) {
2015
2196
  switch (category) {
@@ -2023,23 +2204,6 @@ function getStatusClasses(category) {
2023
2204
  return "text-muted-foreground";
2024
2205
  }
2025
2206
  }
2026
- function parseResponse(text, apiFormat) {
2027
- try {
2028
- let json = JSON.parse(text);
2029
- if (typeof json === "string") {
2030
- json = JSON.parse(json);
2031
- }
2032
- if (apiFormat === "openai") {
2033
- const result2 = parseOpenAIResponse(text);
2034
- if (result2) return result2;
2035
- }
2036
- const result = InspectorResponseSchema.safeParse(json);
2037
- if (result.success) return result.data;
2038
- return null;
2039
- } catch {
2040
- return null;
2041
- }
2042
- }
2043
2207
  function StatusIndicator({ status }) {
2044
2208
  const category = getStatusCategory(status);
2045
2209
  const classes = getStatusClasses(category);
@@ -2057,7 +2221,7 @@ function ErrorResponseView({ text }) {
2057
2221
  function MarkdownFallbackView({ text }) {
2058
2222
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none [&_pre]:bg-muted [&_pre]:text-foreground [&_code]:text-[0.8em] [&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { children: text }) });
2059
2223
  }
2060
- function ResponseView({
2224
+ const ResponseView = reactExports.memo(function ResponseView2({
2061
2225
  responseText,
2062
2226
  responseStatus,
2063
2227
  streaming,
@@ -2068,6 +2232,11 @@ function ResponseView({
2068
2232
  apiFormat,
2069
2233
  error
2070
2234
  }) {
2235
+ const resolvedFormat = apiFormat ?? "unknown";
2236
+ const parsed = reactExports.useMemo(
2237
+ () => getLogFormatAdapter(resolvedFormat).analyzeResponse(responseText).parsed,
2238
+ [resolvedFormat, responseText]
2239
+ );
2071
2240
  if (responseText === null && error === void 0) {
2072
2241
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 py-3", children: [
2073
2242
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
@@ -2095,11 +2264,10 @@ function ResponseView({
2095
2264
  responseText !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorResponseView, { text: responseText }) })
2096
2265
  ] });
2097
2266
  }
2098
- const parsed = responseText !== null ? parseResponse(responseText, apiFormat) : null;
2099
2267
  if (parsed !== null) {
2100
2268
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
2101
2269
  /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIndicator, { status: responseStatus }),
2102
- formatViewFor(apiFormat, parsed)
2270
+ formatViewFor(resolvedFormat, parsed)
2103
2271
  ] });
2104
2272
  }
2105
2273
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
@@ -2126,7 +2294,7 @@ function ResponseView({
2126
2294
  ] }),
2127
2295
  /* @__PURE__ */ jsxRuntimeExports.jsx(MarkdownFallbackView, { text: responseText ?? "" })
2128
2296
  ] });
2129
- }
2297
+ });
2130
2298
  const ReplayResultSchema = object({
2131
2299
  success: boolean(),
2132
2300
  error: string().optional(),
@@ -2276,7 +2444,7 @@ function ReplayDialog({ log, open, onOpenChange }) {
2276
2444
  ] })
2277
2445
  ] }) });
2278
2446
  }
2279
- function StreamingChunkSequence({
2447
+ const StreamingChunkSequence = reactExports.memo(function StreamingChunkSequence2({
2280
2448
  logId,
2281
2449
  truncated
2282
2450
  }) {
@@ -2408,7 +2576,7 @@ function StreamingChunkSequence({
2408
2576
  ] }) }),
2409
2577
  containerExpanded === true ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-border bg-muted/20 overflow-auto max-h-64", children: renderBody() }) : null
2410
2578
  ] });
2411
- }
2579
+ });
2412
2580
  function shouldShowRawRequestTab(apiFormat, viewMode, strip) {
2413
2581
  return apiFormat === "anthropic" && viewMode === "full" && strip;
2414
2582
  }
@@ -2445,7 +2613,7 @@ function DiffToggleButton({
2445
2613
  active,
2446
2614
  onClick
2447
2615
  }) {
2448
- return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2616
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2449
2617
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2450
2618
  "button",
2451
2619
  {
@@ -2463,7 +2631,49 @@ function DiffToggleButton({
2463
2631
  }
2464
2632
  ) }),
2465
2633
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: active ? "Hide diff view" : "Compare proxy output against the original raw version" })
2466
- ] }) });
2634
+ ] });
2635
+ }
2636
+ const RequestDiffContent = reactExports.memo(function({
2637
+ rawBody,
2638
+ displayedBody,
2639
+ emptyLabel
2640
+ }) {
2641
+ const result = reactExports.useMemo(
2642
+ () => computeRequestDiff(rawBody, displayedBody),
2643
+ [rawBody, displayedBody]
2644
+ );
2645
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(DiffView, { result, emptyLabel });
2646
+ });
2647
+ const HeadersDiffContent = reactExports.memo(function({
2648
+ rawHeaders,
2649
+ headers,
2650
+ emptyLabel
2651
+ }) {
2652
+ const result = reactExports.useMemo(() => computeHeadersDiff(rawHeaders, headers), [rawHeaders, headers]);
2653
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(DiffView, { result, emptyLabel });
2654
+ });
2655
+ function useCopyFeedback(text) {
2656
+ const [copied, setCopied] = reactExports.useState(false);
2657
+ const timerRef = reactExports.useRef(null);
2658
+ reactExports.useEffect(
2659
+ () => () => {
2660
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
2661
+ },
2662
+ []
2663
+ );
2664
+ const copy = reactExports.useCallback(
2665
+ (event) => {
2666
+ event.stopPropagation();
2667
+ if (text === null) return;
2668
+ void window.navigator.clipboard.writeText(text).then(() => {
2669
+ setCopied(true);
2670
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
2671
+ timerRef.current = setTimeout(() => setCopied(false), 2e3);
2672
+ });
2673
+ },
2674
+ [text]
2675
+ );
2676
+ return { copied, copy };
2467
2677
  }
2468
2678
  const LogEntry = reactExports.memo(function({
2469
2679
  log,
@@ -2473,156 +2683,81 @@ const LogEntry = reactExports.memo(function({
2473
2683
  onCompareWithPrevious
2474
2684
  }) {
2475
2685
  const [expanded, setExpanded] = reactExports.useState(false);
2476
- const [requestCopied, setRequestCopied] = reactExports.useState(false);
2477
- const [rawRequestCopied, setRawRequestCopied] = reactExports.useState(false);
2478
- const [responseCopied, setResponseCopied] = reactExports.useState(false);
2479
2686
  const [replayOpen, setReplayOpen] = reactExports.useState(false);
2480
2687
  const [headersDiff, setHeadersDiff] = reactExports.useState(false);
2481
2688
  const [requestDiff, setRequestDiff] = reactExports.useState(false);
2482
- const messageCount = reactExports.useMemo(() => {
2483
- if (log.rawRequestBody === null) return null;
2484
- if (log.apiFormat === "anthropic") {
2485
- const parsed = parseRequest(log.rawRequestBody);
2486
- if (parsed !== null) return parsed.messages.length;
2487
- } else if (log.apiFormat === "openai") {
2488
- try {
2489
- const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2490
- if (result.success) return result.data.messages.length;
2491
- } catch {
2492
- }
2493
- }
2494
- return null;
2495
- }, [log.rawRequestBody, log.apiFormat]);
2496
- const toolCount = reactExports.useMemo(() => {
2497
- if (log.rawRequestBody === null) return null;
2498
- if (log.apiFormat === "anthropic") {
2499
- const parsed = parseRequest(log.rawRequestBody);
2500
- if (parsed !== null && parsed.tools !== void 0 && parsed.tools.length > 0) {
2501
- return parsed.tools.length;
2502
- }
2503
- } else if (log.apiFormat === "openai") {
2504
- try {
2505
- const result = OpenAIRequestSchema.safeParse(JSON.parse(log.rawRequestBody));
2506
- if (result.success && result.data.tools !== void 0 && result.data.tools.length > 0) {
2507
- return result.data.tools.length;
2508
- }
2509
- } catch {
2510
- }
2511
- }
2512
- return null;
2513
- }, [log.rawRequestBody, log.apiFormat]);
2514
- const responseToolNames = reactExports.useMemo(() => {
2515
- if (log.responseText === null) return null;
2516
- if (log.apiFormat === "openai") {
2517
- const parsed = parseOpenAIResponse(log.responseText);
2518
- if (parsed !== null) {
2519
- const toolCalls = parsed.choices[0]?.message?.tool_calls;
2520
- if (toolCalls !== void 0 && toolCalls !== null && toolCalls.length > 0) {
2521
- return toolCalls.map((tc) => tc.function?.name ?? "?").filter((n) => n !== "");
2522
- }
2523
- }
2524
- } else if (log.apiFormat === "anthropic") {
2525
- try {
2526
- const result = InspectorResponseSchema.safeParse(JSON.parse(log.responseText));
2527
- if (result.success) {
2528
- const names = result.data.content.filter((c) => c.type === "tool_use").map((c) => c.name);
2529
- if (names.length > 0) return names;
2530
- }
2531
- } catch {
2532
- }
2533
- }
2534
- return null;
2535
- }, [log.responseText, log.apiFormat]);
2689
+ const [activeTab, setActiveTab] = reactExports.useState("request");
2690
+ const resolvedFormat = resolveLogFormat(log);
2691
+ const adapter = getLogFormatAdapter(resolvedFormat);
2692
+ const requestAnalysis = reactExports.useMemo(
2693
+ () => adapter.analyzeRequest(log.rawRequestBody),
2694
+ [adapter, log.rawRequestBody]
2695
+ );
2696
+ const responseAnalysis = reactExports.useMemo(
2697
+ () => adapter.analyzeResponse(log.responseText),
2698
+ [adapter, log.responseText]
2699
+ );
2536
2700
  const strippedRequestBody = reactExports.useMemo(() => {
2537
- if (!strip || log.apiFormat !== "anthropic" || log.rawRequestBody === null) {
2701
+ if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
2538
2702
  return null;
2539
2703
  }
2540
2704
  return stripClaudeCodeBillingHeader(log.rawRequestBody).body;
2541
- }, [log.rawRequestBody, log.apiFormat, strip]);
2705
+ }, [log.rawRequestBody, resolvedFormat, strip]);
2542
2706
  const displayedRequestBody = strippedRequestBody ?? log.rawRequestBody;
2543
- const headersDiffResult = reactExports.useMemo(
2544
- () => computeHeadersDiff(log.rawHeaders, log.headers),
2545
- [log.rawHeaders, log.headers]
2546
- );
2547
- const requestDiffResult = reactExports.useMemo(
2548
- () => computeRequestDiff(log.rawRequestBody, displayedRequestBody),
2549
- [log.rawRequestBody, displayedRequestBody]
2550
- );
2551
- function handleCopyRequest(e) {
2552
- e.stopPropagation();
2553
- if (displayedRequestBody === null) return;
2554
- void window.navigator.clipboard.writeText(displayedRequestBody).then(() => {
2555
- setRequestCopied(true);
2556
- setTimeout(() => setRequestCopied(false), 2e3);
2557
- });
2558
- }
2559
- function handleCopyRawRequest(e) {
2560
- e.stopPropagation();
2561
- if (log.rawRequestBody === null) return;
2562
- void window.navigator.clipboard.writeText(log.rawRequestBody).then(() => {
2563
- setRawRequestCopied(true);
2564
- setTimeout(() => setRawRequestCopied(false), 2e3);
2565
- });
2566
- }
2567
- function handleCopyResponse(e) {
2568
- e.stopPropagation();
2569
- if (log.responseText === null) return;
2570
- void window.navigator.clipboard.writeText(log.responseText).then(() => {
2571
- setResponseCopied(true);
2572
- setTimeout(() => setResponseCopied(false), 2e3);
2573
- });
2574
- }
2575
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2576
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border rounded-lg mb-3 overflow-hidden", children: [
2707
+ const requestCopy = useCopyFeedback(displayedRequestBody);
2708
+ const rawRequestCopy = useCopyFeedback(log.rawRequestBody);
2709
+ const responseCopy = useCopyFeedback(log.responseText);
2710
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(TooltipProvider, { children: [
2711
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-border rounded-lg mb-1 overflow-hidden", children: [
2577
2712
  /* @__PURE__ */ jsxRuntimeExports.jsx(
2578
2713
  LogEntryHeader,
2579
2714
  {
2580
2715
  log,
2581
- messageCount,
2582
- toolCount,
2716
+ messageCount: requestAnalysis.messageCount,
2717
+ toolCount: requestAnalysis.toolCount,
2583
2718
  expanded,
2584
2719
  onToggle: () => setExpanded(!expanded),
2585
- responseToolNames,
2720
+ responseToolNames: responseAnalysis.toolNames,
2586
2721
  cacheTrend
2587
2722
  }
2588
2723
  ),
2589
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { defaultValue: "request", children: [
2724
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [
2590
2725
  /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { className: "mx-4 mt-2", children: [
2591
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2726
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2592
2727
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-headers", children: "Raw Headers" }) }),
2593
2728
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "HTTP headers received from the upstream provider" })
2594
- ] }) }),
2595
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2729
+ ] }),
2730
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2596
2731
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "headers", children: "Headers" }) }),
2597
2732
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Request and response headers sent and received" })
2598
- ] }) }),
2599
- shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2733
+ ] }),
2734
+ shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2600
2735
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw-request", children: "Raw Request" }) }),
2601
2736
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP request sent to the upstream provider" })
2602
- ] }) }),
2737
+ ] }),
2603
2738
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "request", children: "Request" }),
2604
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2739
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2605
2740
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "raw", children: "Raw Response" }) }),
2606
2741
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Exact HTTP response from the upstream provider" })
2607
- ] }) }),
2742
+ ] }),
2608
2743
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "parsed", children: "Response" })
2609
2744
  ] }),
2610
- shouldShowRawRequestTab(log.apiFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2745
+ shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-request", children: activeTab === "raw-request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2611
2746
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2612
2747
  CopyButton,
2613
2748
  {
2614
2749
  text: log.rawRequestBody,
2615
2750
  label: "Copy Raw Request",
2616
- copied: rawRequestCopied,
2617
- onCopy: handleCopyRawRequest
2751
+ copied: rawRequestCopy.copied,
2752
+ onCopy: rawRequestCopy.copy
2618
2753
  }
2619
2754
  ) }),
2620
2755
  log.rawRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.rawRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2621
2756
  ] }) }),
2622
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2757
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "request", children: activeTab === "request" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2623
2758
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mb-2", children: [
2624
2759
  shouldShowRequestDiffButton(
2625
- log.apiFormat,
2760
+ resolvedFormat,
2626
2761
  viewMode,
2627
2762
  strip,
2628
2763
  log.rawRequestBody !== null
@@ -2636,7 +2771,7 @@ const LogEntry = reactExports.memo(function({
2636
2771
  }
2637
2772
  }
2638
2773
  ),
2639
- onCompareWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2774
+ onCompareWithPrevious !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2640
2775
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2641
2776
  Button,
2642
2777
  {
@@ -2645,7 +2780,7 @@ const LogEntry = reactExports.memo(function({
2645
2780
  className: "h-7 text-xs",
2646
2781
  onClick: (e) => {
2647
2782
  e.stopPropagation();
2648
- onCompareWithPrevious();
2783
+ onCompareWithPrevious(log);
2649
2784
  },
2650
2785
  children: [
2651
2786
  /* @__PURE__ */ jsxRuntimeExports.jsx(GitCompareArrows, { className: "size-3 mr-1" }),
@@ -2654,8 +2789,8 @@ const LogEntry = reactExports.memo(function({
2654
2789
  }
2655
2790
  ) }),
2656
2791
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Compare this request with the immediately preceding one" })
2657
- ] }) }),
2658
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2792
+ ] }),
2793
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
2659
2794
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
2660
2795
  Button,
2661
2796
  {
@@ -2673,26 +2808,27 @@ const LogEntry = reactExports.memo(function({
2673
2808
  }
2674
2809
  ) }),
2675
2810
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Re-send this request to the provider" })
2676
- ] }) }),
2811
+ ] }),
2677
2812
  /* @__PURE__ */ jsxRuntimeExports.jsx(
2678
2813
  CopyButton,
2679
2814
  {
2680
2815
  text: displayedRequestBody,
2681
2816
  label: "Copy Request",
2682
- copied: requestCopied,
2683
- onCopy: handleCopyRequest
2817
+ copied: requestCopy.copied,
2818
+ onCopy: requestCopy.copy
2684
2819
  }
2685
2820
  )
2686
2821
  ] }),
2687
2822
  requestDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2688
- DiffView,
2823
+ RequestDiffContent,
2689
2824
  {
2690
- result: requestDiffResult,
2825
+ rawBody: log.rawRequestBody,
2826
+ displayedBody: displayedRequestBody,
2691
2827
  emptyLabel: "No transformation applied — raw and sent request bodies are identical."
2692
2828
  }
2693
2829
  ) : displayedRequestBody !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: displayedRequestBody, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No request body" })
2694
2830
  ] }) }),
2695
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2831
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "headers", children: activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3", children: [
2696
2832
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end gap-2 mb-2", children: shouldShowHeadersDiffButton(
2697
2833
  viewMode,
2698
2834
  log.rawHeaders !== void 0 && Object.keys(log.rawHeaders).length > 0
@@ -2707,9 +2843,10 @@ const LogEntry = reactExports.memo(function({
2707
2843
  }
2708
2844
  ) }),
2709
2845
  headersDiff ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2710
- DiffView,
2846
+ HeadersDiffContent,
2711
2847
  {
2712
- result: headersDiffResult,
2848
+ rawHeaders: log.rawHeaders,
2849
+ headers: log.headers,
2713
2850
  emptyLabel: "No transformation applied — raw and processed headers are identical."
2714
2851
  }
2715
2852
  ) : log.headers && Object.keys(log.headers).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.headers).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
@@ -2720,14 +2857,14 @@ const LogEntry = reactExports.memo(function({
2720
2857
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2721
2858
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No headers captured" })
2722
2859
  ] }) }),
2723
- viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2860
+ viewMode === "full" && /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw-headers", children: activeTab === "raw-headers" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: log.rawHeaders && Object.keys(log.rawHeaders).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 font-mono text-xs", children: Object.entries(log.rawHeaders).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
2724
2861
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-blue-600 dark:text-blue-400 font-semibold shrink-0", children: [
2725
2862
  key,
2726
2863
  ":"
2727
2864
  ] }),
2728
2865
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground truncate", title: value, children: value })
2729
2866
  ] }, key)) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No raw headers captured" }) }) }),
2730
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2867
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "raw", children: activeTab === "raw" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", children: [
2731
2868
  log.error !== void 0 && log.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-destructive/50 bg-destructive/10 p-3 text-xs", children: [
2732
2869
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-semibold text-destructive mb-1", children: "SSE Error" }),
2733
2870
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground font-mono", children: log.error })
@@ -2737,8 +2874,8 @@ const LogEntry = reactExports.memo(function({
2737
2874
  {
2738
2875
  text: log.responseText,
2739
2876
  label: "Copy Response",
2740
- copied: responseCopied,
2741
- onCopy: handleCopyResponse
2877
+ copied: responseCopy.copied,
2878
+ onCopy: responseCopy.copy
2742
2879
  }
2743
2880
  ) }),
2744
2881
  log.responseText !== null ? /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewerFromString, { text: log.responseText, defaultExpandDepth: 1 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground italic", children: "No response" }),
@@ -2750,7 +2887,7 @@ const LogEntry = reactExports.memo(function({
2750
2887
  }
2751
2888
  )
2752
2889
  ] }) }),
2753
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2890
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "parsed", children: activeTab === "parsed" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2754
2891
  ResponseView,
2755
2892
  {
2756
2893
  responseText: log.responseText,
@@ -2760,7 +2897,7 @@ const LogEntry = reactExports.memo(function({
2760
2897
  outputTokens: log.outputTokens,
2761
2898
  cacheCreationInputTokens: log.cacheCreationInputTokens,
2762
2899
  cacheReadInputTokens: log.cacheReadInputTokens,
2763
- apiFormat: log.apiFormat,
2900
+ apiFormat: resolvedFormat,
2764
2901
  error: log.error
2765
2902
  }
2766
2903
  ) }) })
@@ -3173,37 +3310,37 @@ function ThreadConnector({
3173
3310
  ) })
3174
3311
  ] });
3175
3312
  }
3176
- function TurnGroup({
3177
- logs,
3313
+ const TurnGroup = reactExports.memo(function TurnGroup2({
3314
+ entries,
3178
3315
  viewMode,
3179
3316
  strip,
3180
3317
  cacheTrends,
3181
3318
  onCompareWithPrevious,
3319
+ comparisonPredecessors,
3182
3320
  turnIndex = 0
3183
3321
  }) {
3184
- const stopReasons = reactExports.useMemo(() => logs.map((l) => extractStopReason(l)), [logs]);
3185
- const lastIdx = logs.length - 1;
3186
- const lastStop = stopReasons[lastIdx] ?? null;
3322
+ const lastIdx = entries.length - 1;
3323
+ const lastStop = entries[lastIdx]?.stopReason ?? null;
3187
3324
  const isComplete = lastStop !== null ? isTurnBoundary(lastStop) : false;
3188
- const isPending = logs[lastIdx]?.responseStatus === null;
3325
+ const isPending = entries[lastIdx]?.log.responseStatus === null;
3189
3326
  const collapsible = isComplete && !isPending;
3190
3327
  const [collapsed, setCollapsed] = reactExports.useState(false);
3191
3328
  const toggleCollapse = reactExports.useCallback(() => {
3192
3329
  if (collapsible) setCollapsed((prev) => !prev);
3193
3330
  }, [collapsible]);
3194
- const visibleLogs = reactExports.useMemo(() => {
3195
- if (!collapsed) return logs;
3196
- const last = logs[lastIdx];
3331
+ const visibleEntries = reactExports.useMemo(() => {
3332
+ if (!collapsed) return entries;
3333
+ const last = entries[lastIdx];
3197
3334
  return last !== void 0 ? [last] : [];
3198
- }, [logs, collapsed, lastIdx]);
3335
+ }, [entries, collapsed, lastIdx]);
3199
3336
  const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
3200
3337
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
3201
3338
  "div",
3202
3339
  {
3203
3340
  className: cn("border rounded-lg", isPending ? "border-amber-500/10" : "border-transparent"),
3204
- children: visibleLogs.map((log, visibleIdx) => {
3341
+ children: visibleEntries.map((entry, visibleIdx) => {
3205
3342
  const originalIdx = collapsed ? lastIdx : visibleIdx;
3206
- const reason = stopReasons[originalIdx] ?? null;
3343
+ const { log, stopReason: reason } = entry;
3207
3344
  const isBoundary = reason === "end_turn" || reason === "stop";
3208
3345
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-stretch", children: [
3209
3346
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -3214,25 +3351,59 @@ function TurnGroup({
3214
3351
  isFirst: originalIdx === 0,
3215
3352
  isTurnStart: originalIdx === 0,
3216
3353
  crabIndex: log.id % 12,
3217
- collapsible: collapsible && isBoundary && logs.length > 1,
3354
+ collapsible: collapsible && isBoundary && entries.length > 1,
3218
3355
  collapsed,
3219
3356
  onToggle: toggleCollapse
3220
3357
  }
3221
3358
  ),
3222
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex-1 min-w-0 mb-2 rounded-lg", bgClass), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3359
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3223
3360
  LogEntry,
3224
3361
  {
3225
3362
  log,
3226
3363
  viewMode,
3227
3364
  strip,
3228
3365
  cacheTrend: cacheTrends?.get(log.id) ?? null,
3229
- onCompareWithPrevious: () => onCompareWithPrevious(log)
3366
+ onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3230
3367
  }
3231
3368
  ) })
3232
3369
  ] }, log.id);
3233
3370
  })
3234
3371
  }
3235
3372
  );
3373
+ });
3374
+ function shouldRenderConversationContent(standalone, expanded) {
3375
+ return standalone || expanded;
3376
+ }
3377
+ function buildTurnGroups(logs) {
3378
+ const groups = [];
3379
+ let entries = [];
3380
+ let turnIndex = 0;
3381
+ for (const log of logs) {
3382
+ entries.push({ log, stopReason: extractStopReason(log) });
3383
+ const current = entries[entries.length - 1];
3384
+ if (current !== void 0 && isTurnBoundary(current.stopReason)) {
3385
+ groups.push({ entries, turnIndex });
3386
+ entries = [];
3387
+ turnIndex += 1;
3388
+ }
3389
+ }
3390
+ if (entries.length > 0) groups.push({ entries, turnIndex });
3391
+ return groups;
3392
+ }
3393
+ function buildValidPredecessors(groups) {
3394
+ const predecessors = /* @__PURE__ */ new Map();
3395
+ for (const group of groups) {
3396
+ for (let index = 1; index < group.logs.length; index += 1) {
3397
+ const current = group.logs[index];
3398
+ const previous = group.logs[index - 1];
3399
+ if (current === void 0 || previous === void 0) continue;
3400
+ const currentFormat = resolveLogFormat(current);
3401
+ const previousFormat = resolveLogFormat(previous);
3402
+ if (currentFormat === "unknown" || currentFormat !== previousFormat) continue;
3403
+ predecessors.set(current.id, previous);
3404
+ }
3405
+ }
3406
+ return predecessors;
3236
3407
  }
3237
3408
  function computeStats(logs) {
3238
3409
  let totalInput = 0;
@@ -3249,51 +3420,19 @@ const ConversationGroup = reactExports.memo(function({
3249
3420
  strip,
3250
3421
  cacheTrends,
3251
3422
  onCompareWithPrevious,
3423
+ comparisonPredecessors,
3424
+ onClearGroup,
3252
3425
  standalone = false
3253
3426
  }) {
3254
- const [expanded, setExpanded] = reactExports.useState(standalone);
3255
- const stats = computeStats(group.logs);
3427
+ const [expanded, setExpanded] = reactExports.useState(false);
3428
+ const stats = reactExports.useMemo(() => computeStats(group.logs), [group.logs]);
3256
3429
  const startTime = group.logs[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
3257
3430
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
3258
3431
  const mixed = hasMixedApiFormat(group.logs);
3259
3432
  const isLoading = group.logs.some((log) => log.responseStatus === null);
3260
- const stopReasons = reactExports.useMemo(() => group.logs.map((log) => extractStopReason(log)), [group.logs]);
3261
- const turnIndices = reactExports.useMemo(() => {
3262
- const indices = [];
3263
- let turn = 0;
3264
- for (let i = 0; i < stopReasons.length; i++) {
3265
- if (i > 0 && (stopReasons[i - 1] === "end_turn" || stopReasons[i - 1] === "stop")) {
3266
- turn++;
3267
- }
3268
- indices.push(turn);
3269
- }
3270
- return indices;
3271
- }, [stopReasons]);
3272
- const turnGroups = reactExports.useMemo(() => {
3273
- const groups = [];
3274
- let currentGroup = [];
3275
- let currentTurn = 0;
3276
- for (let i = 0; i < group.logs.length; i++) {
3277
- const turnIdx = turnIndices[i] ?? 0;
3278
- const log = group.logs[i];
3279
- if (!log) continue;
3280
- if (turnIdx !== currentTurn) {
3281
- if (currentGroup.length > 0) {
3282
- groups.push({ logs: currentGroup, turnIndex: currentTurn });
3283
- }
3284
- currentGroup = [log];
3285
- currentTurn = turnIdx;
3286
- } else {
3287
- currentGroup.push(log);
3288
- }
3289
- }
3290
- if (currentGroup.length > 0) {
3291
- groups.push({ logs: currentGroup, turnIndex: currentTurn });
3292
- }
3293
- return groups;
3294
- }, [group.logs, turnIndices]);
3433
+ const turnGroups = reactExports.useMemo(() => buildTurnGroups(group.logs), [group.logs]);
3295
3434
  const displayId = group.conversationId.startsWith("PID:") || group.conversationId.includes("|") ? group.conversationId : group.conversationId.length > 24 ? group.conversationId.slice(0, 12) + "…" + group.conversationId.slice(-12) : group.conversationId;
3296
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
3435
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2", children: [
3297
3436
  !standalone && /* @__PURE__ */ jsxRuntimeExports.jsx(
3298
3437
  ConversationHeader,
3299
3438
  {
@@ -3308,17 +3447,19 @@ const ConversationGroup = reactExports.memo(function({
3308
3447
  onToggle: () => setExpanded(!expanded),
3309
3448
  hideApiFormat: mixed,
3310
3449
  isLoading,
3311
- userAgent: group.logs[0]?.userAgent ?? null
3450
+ userAgent: group.logs[0]?.userAgent ?? null,
3451
+ onClear: () => onClearGroup(group.logs.map((l) => l.id))
3312
3452
  }
3313
3453
  ),
3314
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3454
+ shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-[70vh] overflow-y-auto", children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3315
3455
  TurnGroup,
3316
3456
  {
3317
- logs: tg.logs,
3457
+ entries: tg.entries,
3318
3458
  viewMode,
3319
3459
  strip,
3320
3460
  cacheTrends,
3321
3461
  onCompareWithPrevious,
3462
+ comparisonPredecessors,
3322
3463
  turnIndex: tg.turnIndex
3323
3464
  },
3324
3465
  tg.turnIndex
@@ -3509,6 +3650,32 @@ function OpenCodeIcon() {
3509
3650
  }
3510
3651
  );
3511
3652
  }
3653
+ function MiMoCodeIcon() {
3654
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
3655
+ "svg",
3656
+ {
3657
+ fill: "currentColor",
3658
+ viewBox: "0 0 24 24",
3659
+ xmlns: "http://www.w3.org/2000/svg",
3660
+ className: "size-6 shrink-0",
3661
+ children: [
3662
+ /* @__PURE__ */ jsxRuntimeExports.jsx("title", { children: "MiMo Code" }),
3663
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3664
+ "text",
3665
+ {
3666
+ x: "12",
3667
+ y: "18",
3668
+ textAnchor: "middle",
3669
+ fontSize: "16",
3670
+ fontWeight: "800",
3671
+ fontFamily: "system-ui, sans-serif",
3672
+ children: "M"
3673
+ }
3674
+ )
3675
+ ]
3676
+ }
3677
+ );
3678
+ }
3512
3679
  const ExternalProviderSchema = object({
3513
3680
  name: string(),
3514
3681
  apiKey: string(),
@@ -3516,7 +3683,7 @@ const ExternalProviderSchema = object({
3516
3683
  anthropicBaseUrl: string(),
3517
3684
  openaiBaseUrl: string(),
3518
3685
  models: array(string()),
3519
- sourceTool: _enum(["claude-code", "opencode"]),
3686
+ sourceTool: _enum(["claude-code", "opencode", "mimo-code"]),
3520
3687
  alreadyExists: boolean()
3521
3688
  });
3522
3689
  const ScanResponseSchema = object({
@@ -3531,7 +3698,8 @@ const ImportResponseSchema$1 = object({
3531
3698
  });
3532
3699
  const sourceLogoMap = {
3533
3700
  "claude-code": ClaudeCodeIcon,
3534
- opencode: OpenCodeIcon
3701
+ opencode: OpenCodeIcon,
3702
+ "mimo-code": MiMoCodeIcon
3535
3703
  };
3536
3704
  function ImportWizardDialog({
3537
3705
  open,
@@ -3645,7 +3813,7 @@ function ImportWizardDialog({
3645
3813
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { className: "max-w-xl max-h-[80vh] overflow-hidden flex flex-col", children: [
3646
3814
  /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogHeader, { children: [
3647
3815
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Import from External Tools" }),
3648
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Detect provider configurations from Claude Code and OpenCode." })
3816
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Detect provider configurations from Claude Code, OpenCode, and MiMo Code." })
3649
3817
  ] }),
3650
3818
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto space-y-3", children: [
3651
3819
  scanning && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-center py-8 gap-2 text-muted-foreground", children: [
@@ -3659,7 +3827,7 @@ function ImportWizardDialog({
3659
3827
  !scanning && scanError === null && providers.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-muted-foreground py-8 text-center", children: [
3660
3828
  "No external provider configurations found.",
3661
3829
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
3662
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "Supported tools: Claude Code (~/.claude/settings.json), OpenCode (~/.config/opencode/opencode.json)" })
3830
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "Supported tools: Claude Code (~/.claude/settings.json), OpenCode (~/.config/opencode/opencode.json), MiMo Code (~/.config/mimocode/mimocode.jsonc)" })
3663
3831
  ] }),
3664
3832
  !scanning && providers.map((p, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3665
3833
  "label",
@@ -3723,35 +3891,6 @@ function ImportWizardDialog({
3723
3891
  ] })
3724
3892
  ] }) });
3725
3893
  }
3726
- function ConfirmDialog({
3727
- open,
3728
- onOpenChange,
3729
- title,
3730
- description,
3731
- confirmLabel = "Confirm",
3732
- variant = "default",
3733
- onConfirm
3734
- }) {
3735
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
3736
- /* @__PURE__ */ jsxRuntimeExports.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: title }) }),
3737
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: description }),
3738
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [
3739
- /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false), children: "Cancel" }),
3740
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3741
- Button,
3742
- {
3743
- variant,
3744
- size: "sm",
3745
- onClick: () => {
3746
- onConfirm();
3747
- onOpenChange(false);
3748
- },
3749
- children: confirmLabel
3750
- }
3751
- )
3752
- ] })
3753
- ] }) });
3754
- }
3755
3894
  function maskApiKey(apiKey) {
3756
3895
  if (apiKey.length <= 8) return "••••••••";
3757
3896
  return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
@@ -5776,10 +5915,23 @@ function SideSummary({ log, side }) {
5776
5915
  }
5777
5916
  function CompareDrawer({ left, right, onClose }) {
5778
5917
  const ops = reactExports.useMemo(() => {
5779
- const l = normalizeRequest(parseRequest(left.rawRequestBody) ?? left.rawRequestBody);
5780
- const r = normalizeRequest(parseRequest(right.rawRequestBody) ?? right.rawRequestBody);
5918
+ const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
5919
+ left.rawRequestBody
5920
+ );
5921
+ const rightRequest = getLogFormatAdapter(resolveLogFormat(right)).analyzeRequest(
5922
+ right.rawRequestBody
5923
+ );
5924
+ const l = normalizeRequest(leftRequest.comparisonValue);
5925
+ const r = normalizeRequest(rightRequest.comparisonValue);
5781
5926
  return diffTrees(l, r);
5782
- }, [left.rawRequestBody, right.rawRequestBody]);
5927
+ }, [
5928
+ left.apiFormat,
5929
+ left.path,
5930
+ left.rawRequestBody,
5931
+ right.apiFormat,
5932
+ right.path,
5933
+ right.rawRequestBody
5934
+ ]);
5783
5935
  const grouped = reactExports.useMemo(() => groupContiguousEquals(ops), [ops]);
5784
5936
  const counts = reactExports.useMemo(() => {
5785
5937
  let added = 0;
@@ -6116,13 +6268,28 @@ function ProxyViewer({
6116
6268
  onSessionChange,
6117
6269
  onModelChange,
6118
6270
  onClearAll,
6271
+ onClearGroup,
6119
6272
  viewMode,
6120
6273
  onViewModeChange,
6121
6274
  strip
6122
6275
  }) {
6123
- const { totalIn, totalOut } = computeTokenSummary(logs);
6276
+ const { totalIn, totalOut } = reactExports.useMemo(() => computeTokenSummary(logs), [logs]);
6124
6277
  const [exporting, setExporting] = reactExports.useState(false);
6125
6278
  const [comparePair, setComparePair] = reactExports.useState(null);
6279
+ const [crabEntrancePhase, setCrabEntrancePhase] = reactExports.useState(
6280
+ "hidden"
6281
+ );
6282
+ reactExports.useEffect(() => {
6283
+ const perCrabDuration = 400;
6284
+ const startDelay = 50;
6285
+ const playingDone = startDelay + crabVariants.length * perCrabDuration;
6286
+ const t1 = setTimeout(() => setCrabEntrancePhase("playing"), startDelay);
6287
+ const t2 = setTimeout(() => setCrabEntrancePhase("done"), playingDone);
6288
+ return () => {
6289
+ clearTimeout(t1);
6290
+ clearTimeout(t2);
6291
+ };
6292
+ }, []);
6126
6293
  const handleExport = reactExports.useCallback(async () => {
6127
6294
  setExporting(true);
6128
6295
  try {
@@ -6131,58 +6298,55 @@ function ProxyViewer({
6131
6298
  setExporting(false);
6132
6299
  }
6133
6300
  }, [logs]);
6134
- const parentRef = reactExports.useRef(null);
6135
6301
  reactExports.useEffect(() => {
6136
6302
  setComparePair(null);
6137
6303
  }, [selectedSession, selectedModel]);
6138
6304
  const closeCompare = reactExports.useCallback(() => {
6139
6305
  setComparePair(null);
6140
6306
  }, []);
6307
+ const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
6308
+ const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
6309
+ const comparisonPredecessors = reactExports.useMemo(() => buildValidPredecessors(groups), [groups]);
6141
6310
  const handleCompareWithPrevious = reactExports.useCallback(
6142
6311
  (log) => {
6143
- const idx = logs.indexOf(log);
6144
- if (idx <= 0) return;
6145
- const predecessor = logs[idx - 1];
6146
- if (predecessor === void 0) return;
6147
- setComparePair([predecessor, log]);
6312
+ const predecessor = comparisonPredecessors.get(log.id);
6313
+ if (predecessor !== void 0) setComparePair([predecessor, log]);
6148
6314
  },
6149
- [logs]
6315
+ [comparisonPredecessors]
6150
6316
  );
6151
- const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
6152
- const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
6153
- const rowVirtualizer = useVirtualizer({
6154
- count: groups.length,
6155
- getScrollElement: () => parentRef.current,
6156
- estimateSize: () => 150,
6157
- measureElement: typeof window !== "undefined" ? (element) => element.getBoundingClientRect().height : void 0,
6158
- overscan: 5
6159
- });
6160
6317
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-[1200px] mx-auto flex flex-col h-screen", style: { maxHeight: "100vh" }, children: [
6161
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end px-6 pt-6 pb-3 relative", children: [
6318
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end px-6 pt-6 pb-8 relative", children: [
6162
6319
  /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-lg font-bold flex items-end gap-2 absolute left-1/2 -translate-x-1/2 whitespace-nowrap", children: [
6163
6320
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-end gap-1 group cursor-default", "aria-hidden": "true", children: [
6164
6321
  /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-10 text-amber-500 transition-all duration-300 group-hover:scale-125 group-hover:-translate-y-1.5" }),
6165
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-end gap-0.5", children: crabVariants.map((Crab, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6166
- Crab,
6167
- {
6168
- className: `size-5 ${[
6169
- "text-amber-500",
6170
- "text-rose-500",
6171
- "text-sky-500",
6172
- "text-emerald-500",
6173
- "text-violet-500",
6174
- "text-orange-500",
6175
- "text-cyan-500",
6176
- "text-pink-500",
6177
- "text-lime-500",
6178
- "text-blue-500",
6179
- "text-yellow-500",
6180
- "text-fuchsia-500"
6181
- ][i]} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1`,
6182
- style: { transitionDelay: `${i * 50}ms` }
6183
- },
6184
- i
6185
- )) })
6322
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex items-end gap-0.5", children: crabVariants.map((Crab, i) => {
6323
+ const color = [
6324
+ "text-amber-500",
6325
+ "text-rose-500",
6326
+ "text-sky-500",
6327
+ "text-emerald-500",
6328
+ "text-violet-500",
6329
+ "text-orange-500",
6330
+ "text-cyan-500",
6331
+ "text-pink-500",
6332
+ "text-lime-500",
6333
+ "text-blue-500",
6334
+ "text-yellow-500",
6335
+ "text-fuchsia-500"
6336
+ ][i];
6337
+ const entranceClass = crabEntrancePhase === "hidden" ? "opacity-0 scale-0" : crabEntrancePhase === "playing" ? "animate-crab-piano-pop" : "";
6338
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
6339
+ Crab,
6340
+ {
6341
+ className: `size-5 ${color} transition-all duration-300 ease-out group-hover:scale-125 group-hover:-translate-y-1 ${entranceClass}`,
6342
+ style: {
6343
+ transitionDelay: `${i * 50}ms`,
6344
+ ...crabEntrancePhase === "playing" ? { animationDelay: `${i * 400}ms` } : {}
6345
+ }
6346
+ },
6347
+ i
6348
+ );
6349
+ }) })
6186
6350
  ] }),
6187
6351
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-baseline gap-2", children: [
6188
6352
  "LLM Inspector",
@@ -6267,59 +6431,26 @@ function ProxyViewer({
6267
6431
  )
6268
6432
  ] }),
6269
6433
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 px-6 pb-6", children: logs.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-muted-foreground py-16 space-y-4", children: [
6270
- /* @__PURE__ */ jsxRuntimeExports.jsx(CrabLogo, { className: "size-12 text-muted-foreground/20 mx-auto" }),
6271
6434
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "No requests captured yet." }),
6272
6435
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs", children: "Route AI coding tools through the proxy:" }),
6273
6436
  /* @__PURE__ */ jsxRuntimeExports.jsx(CopyableCommand, { command: "LLM_BASE_URL=http://localhost:25947/proxy <your-tool>" })
6274
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: parentRef, className: "overflow-y-auto h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6275
- "div",
6437
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto h-full flex flex-col gap-2", children: groups.map((group) => /* @__PURE__ */ jsxRuntimeExports.jsx(
6438
+ ConversationGroup,
6276
6439
  {
6277
- style: {
6278
- height: `${rowVirtualizer.getTotalSize()}px`,
6279
- width: "100%",
6280
- position: "relative"
6281
- },
6282
- children: rowVirtualizer.getVirtualItems().map((virtualRow) => {
6283
- const group = groups[virtualRow.index];
6284
- if (group === void 0) return null;
6285
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
6286
- "div",
6287
- {
6288
- "data-index": virtualRow.index,
6289
- ref: rowVirtualizer.measureElement,
6290
- style: {
6291
- position: "absolute",
6292
- top: 0,
6293
- left: 0,
6294
- width: "100%",
6295
- transform: `translateY(${virtualRow.start}px)`
6296
- },
6297
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
6298
- ConversationGroup,
6299
- {
6300
- group,
6301
- viewMode,
6302
- strip,
6303
- cacheTrends,
6304
- onCompareWithPrevious: handleCompareWithPrevious,
6305
- standalone: groups.length === 1
6306
- }
6307
- )
6308
- },
6309
- group.id
6310
- );
6311
- })
6312
- }
6313
- ) }) }),
6440
+ group,
6441
+ viewMode,
6442
+ strip,
6443
+ cacheTrends,
6444
+ onCompareWithPrevious: handleCompareWithPrevious,
6445
+ comparisonPredecessors,
6446
+ onClearGroup,
6447
+ standalone: groups.length === 1
6448
+ },
6449
+ group.id
6450
+ )) }) }),
6314
6451
  comparePair !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(CompareDrawer, { left: comparePair[0], right: comparePair[1], onClose: closeCompare })
6315
6452
  ] });
6316
6453
  }
6317
- object({
6318
- logs: array(CapturedLogSchema),
6319
- total: number(),
6320
- offset: number(),
6321
- limit: number()
6322
- });
6323
6454
  const SSEUpdateSchema = union([
6324
6455
  object({
6325
6456
  type: literal("init"),
@@ -6344,8 +6475,17 @@ function extractModels(logs) {
6344
6475
  }
6345
6476
  return [...set];
6346
6477
  }
6478
+ function filterLogs(logs, selectedSession, selectedModel) {
6479
+ if (selectedSession === "__all__" && selectedModel === "__all__") return logs;
6480
+ return logs.filter((l) => {
6481
+ if (selectedSession !== "__all__" && l.sessionId !== selectedSession) return false;
6482
+ if (selectedModel !== "__all__" && l.model !== selectedModel) return false;
6483
+ return true;
6484
+ });
6485
+ }
6486
+ const DEBOUNCE_MS = 50;
6347
6487
  function ProxyViewerContainer() {
6348
- const [logs, setLogs] = reactExports.useState([]);
6488
+ const [allLogs, setAllLogs] = reactExports.useState([]);
6349
6489
  const [sessions, setSessions] = reactExports.useState([]);
6350
6490
  const [models, setModels] = reactExports.useState([]);
6351
6491
  const [selectedSession, setSelectedSession] = reactExports.useState("__all__");
@@ -6354,31 +6494,58 @@ function ProxyViewerContainer() {
6354
6494
  const [error, setError] = reactExports.useState(null);
6355
6495
  const eventSourceRef = reactExports.useRef(null);
6356
6496
  const reconnectTimeoutRef = reactExports.useRef(null);
6357
- const fetchSessionsAndModels = reactExports.useCallback(async () => {
6358
- try {
6359
- const [sessionsRes, modelsRes] = await Promise.all([
6360
- fetch("/api/sessions"),
6361
- fetch("/api/models")
6362
- ]);
6363
- if (sessionsRes.ok && modelsRes.ok) {
6364
- const sessionsJson = await sessionsRes.json();
6365
- const modelsJson = await modelsRes.json();
6366
- const sessionsResult = array(string()).safeParse(sessionsJson);
6367
- const modelsResult = array(string()).safeParse(modelsJson);
6368
- if (sessionsResult.success) setSessions(sessionsResult.data);
6369
- if (modelsResult.success) setModels(modelsResult.data);
6497
+ const logIndexRef = reactExports.useRef(/* @__PURE__ */ new Map());
6498
+ const logsRef = reactExports.useRef([]);
6499
+ const pendingUpdatesRef = reactExports.useRef([]);
6500
+ const flushTimerRef = reactExports.useRef(null);
6501
+ const logs = reactExports.useMemo(
6502
+ () => filterLogs(allLogs, selectedSession, selectedModel),
6503
+ [allLogs, selectedSession, selectedModel]
6504
+ );
6505
+ const flushUpdates = reactExports.useCallback(() => {
6506
+ flushTimerRef.current = null;
6507
+ const updates = pendingUpdatesRef.current;
6508
+ pendingUpdatesRef.current = [];
6509
+ if (updates.length === 0) return;
6510
+ setAllLogs((prev) => {
6511
+ let next = prev;
6512
+ let sessionsChanged = false;
6513
+ let modelsChanged = false;
6514
+ for (const log of updates) {
6515
+ const idx = logIndexRef.current.get(log.id);
6516
+ if (idx !== void 0) {
6517
+ next = [...next.slice(0, idx), log, ...next.slice(idx + 1)];
6518
+ } else {
6519
+ logIndexRef.current.set(log.id, next.length);
6520
+ next = [...next, log];
6521
+ }
6522
+ if (log.sessionId !== null && log.sessionId !== "" && !sessions.includes(log.sessionId)) {
6523
+ sessionsChanged = true;
6524
+ }
6525
+ if (log.model !== null && log.model !== "" && !models.includes(log.model)) {
6526
+ modelsChanged = true;
6527
+ }
6370
6528
  }
6371
- } catch {
6372
- }
6373
- }, []);
6529
+ logsRef.current = next;
6530
+ if (sessionsChanged) setSessions((s) => extractSessions(next));
6531
+ if (modelsChanged) setModels((m) => extractModels(next));
6532
+ return next;
6533
+ });
6534
+ }, [sessions, models]);
6535
+ const scheduleUpdate = reactExports.useCallback(
6536
+ (log) => {
6537
+ pendingUpdatesRef.current.push(log);
6538
+ if (flushTimerRef.current === null) {
6539
+ flushTimerRef.current = setTimeout(flushUpdates, DEBOUNCE_MS);
6540
+ }
6541
+ },
6542
+ [flushUpdates]
6543
+ );
6374
6544
  const connectSSE = reactExports.useCallback(() => {
6375
6545
  if (eventSourceRef.current) {
6376
6546
  eventSourceRef.current.close();
6377
6547
  }
6378
- const params = new URLSearchParams();
6379
- if (selectedSession !== "__all__") params.set("sessionId", selectedSession);
6380
- if (selectedModel !== "__all__") params.set("model", selectedModel);
6381
- const es = new EventSource(`/api/logs/stream?${params}`);
6548
+ const es = new EventSource("/api/logs/stream");
6382
6549
  eventSourceRef.current = es;
6383
6550
  es.onmessage = (event) => {
6384
6551
  try {
@@ -6391,35 +6558,24 @@ function ProxyViewerContainer() {
6391
6558
  }
6392
6559
  const update = updateResult.data;
6393
6560
  if (update.type === "init") {
6394
- setLogs(update.logs);
6561
+ if (flushTimerRef.current !== null) {
6562
+ clearTimeout(flushTimerRef.current);
6563
+ flushTimerRef.current = null;
6564
+ }
6565
+ pendingUpdatesRef.current = [];
6566
+ const idx = /* @__PURE__ */ new Map();
6567
+ for (let i = 0; i < update.logs.length; i++) {
6568
+ const log = update.logs[i];
6569
+ if (log !== void 0) idx.set(log.id, i);
6570
+ }
6571
+ logIndexRef.current = idx;
6572
+ logsRef.current = update.logs;
6573
+ setAllLogs(update.logs);
6395
6574
  setSessions(extractSessions(update.logs));
6396
6575
  setModels(extractModels(update.logs));
6397
6576
  setError(null);
6398
6577
  } else if (update.type === "update") {
6399
- setLogs((prev) => {
6400
- const sessionMatch = selectedSession === "__all__" || update.log.sessionId === selectedSession;
6401
- const modelMatch = selectedModel === "__all__" || update.log.model === selectedModel;
6402
- if (!sessionMatch || !modelMatch) return prev;
6403
- const existsIndex = prev.findIndex((l) => l.id === update.log.id);
6404
- if (existsIndex >= 0) {
6405
- return prev.map((l) => l.id === update.log.id ? update.log : l);
6406
- }
6407
- return [...prev, update.log];
6408
- });
6409
- setSessions((prev) => {
6410
- const sessionId = update.log.sessionId;
6411
- if (sessionId !== null && sessionId !== void 0 && sessionId !== "") {
6412
- return prev.includes(sessionId) ? prev : [...prev, sessionId];
6413
- }
6414
- return prev;
6415
- });
6416
- setModels((prev) => {
6417
- const model = update.log.model;
6418
- if (model !== null && model !== void 0 && model !== "") {
6419
- return prev.includes(model) ? prev : [...prev, model];
6420
- }
6421
- return prev;
6422
- });
6578
+ scheduleUpdate(update.log);
6423
6579
  }
6424
6580
  } catch {
6425
6581
  setError("Failed to parse SSE data");
@@ -6433,8 +6589,7 @@ function ProxyViewerContainer() {
6433
6589
  }
6434
6590
  reconnectTimeoutRef.current = setTimeout(connectSSE, 3e3);
6435
6591
  };
6436
- void fetchSessionsAndModels();
6437
- }, [selectedSession, selectedModel, fetchSessionsAndModels]);
6592
+ }, [scheduleUpdate]);
6438
6593
  reactExports.useEffect(() => {
6439
6594
  connectSSE();
6440
6595
  return () => {
@@ -6446,6 +6601,10 @@ function ProxyViewerContainer() {
6446
6601
  clearTimeout(reconnectTimeoutRef.current);
6447
6602
  reconnectTimeoutRef.current = null;
6448
6603
  }
6604
+ if (flushTimerRef.current !== null) {
6605
+ clearTimeout(flushTimerRef.current);
6606
+ flushTimerRef.current = null;
6607
+ }
6449
6608
  };
6450
6609
  }, [connectSSE]);
6451
6610
  const handleClearAll = reactExports.useCallback(() => {
@@ -6456,7 +6615,9 @@ function ProxyViewerContainer() {
6456
6615
  setError("Failed to clear logs");
6457
6616
  return;
6458
6617
  }
6459
- setLogs([]);
6618
+ logIndexRef.current.clear();
6619
+ logsRef.current = [];
6620
+ setAllLogs([]);
6460
6621
  setSessions([]);
6461
6622
  setModels([]);
6462
6623
  setError(null);
@@ -6465,6 +6626,39 @@ function ProxyViewerContainer() {
6465
6626
  }
6466
6627
  })();
6467
6628
  }, []);
6629
+ const handleClearGroup = reactExports.useCallback((ids) => {
6630
+ if (ids.length === 0) return;
6631
+ void (async () => {
6632
+ try {
6633
+ const res = await fetch("/api/logs", {
6634
+ method: "DELETE",
6635
+ headers: { "Content-Type": "application/json" },
6636
+ body: JSON.stringify({ ids })
6637
+ });
6638
+ if (!res.ok) {
6639
+ setError("Failed to clear group");
6640
+ return;
6641
+ }
6642
+ const idSet = new Set(ids);
6643
+ setAllLogs((prev) => {
6644
+ const remaining = prev.filter((l) => !idSet.has(l.id));
6645
+ const idx = /* @__PURE__ */ new Map();
6646
+ for (let i = 0; i < remaining.length; i++) {
6647
+ const log = remaining[i];
6648
+ if (log !== void 0) idx.set(log.id, i);
6649
+ }
6650
+ logIndexRef.current = idx;
6651
+ logsRef.current = remaining;
6652
+ setSessions(extractSessions(remaining));
6653
+ setModels(extractModels(remaining));
6654
+ return remaining;
6655
+ });
6656
+ setError(null);
6657
+ } catch (err) {
6658
+ setError(err instanceof Error ? err.message : "Unknown error clearing group");
6659
+ }
6660
+ })();
6661
+ }, []);
6468
6662
  const { strip } = useStripConfig();
6469
6663
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6470
6664
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed top-4 right-4 bg-destructive text-destructive-foreground px-4 py-2 rounded-md text-sm z-50", children: error }),
@@ -6479,6 +6673,7 @@ function ProxyViewerContainer() {
6479
6673
  onSessionChange: setSelectedSession,
6480
6674
  onModelChange: setSelectedModel,
6481
6675
  onClearAll: handleClearAll,
6676
+ onClearGroup: handleClearGroup,
6482
6677
  viewMode,
6483
6678
  onViewModeChange: setViewMode,
6484
6679
  strip