@tonyclaw/agent-inspector 2.0.36 → 2.0.37

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 (30) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Bg2mo0NZ.js → CompareDrawer-nEHCEVP4.js} +1 -1
  3. package/.output/public/assets/{ProxyViewerContainer-C_Lq5T5V.js → ProxyViewerContainer-BstBIX2B.js} +35 -35
  4. package/.output/public/assets/{ReplayDialog-Bns-ayOg.js → ReplayDialog-CUnBdt1Q.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-BBcPAIWx.js → RequestAnatomy-BU9VT2Sw.js} +1 -1
  6. package/.output/public/assets/{ResponseView-DSRS3rHt.js → ResponseView-amxuKPKY.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-CzZxKE5-.js → StreamingChunkSequence-DpscrQM2.js} +1 -1
  8. package/.output/public/assets/_sessionId-7c9bsOFT.js +1 -0
  9. package/.output/public/assets/index-B7a5OmIO.js +1 -0
  10. package/.output/public/assets/{main-BVKRHBUl.js → main-D56QvEyw.js} +2 -2
  11. package/.output/server/_libs/lucide-react.mjs +114 -94
  12. package/.output/server/{_sessionId-Dsd36Yhc.mjs → _sessionId-ChwEDyI4.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-BbJ5UdYD.mjs → CompareDrawer-Ce2590rK.mjs} +3 -3
  14. package/.output/server/_ssr/{ProxyViewerContainer-7igCY291.mjs → ProxyViewerContainer-D6xM8A0S.mjs} +797 -781
  15. package/.output/server/_ssr/{ReplayDialog-CgWweshq.mjs → ReplayDialog-CB4KOlLe.mjs} +4 -4
  16. package/.output/server/_ssr/{RequestAnatomy-BAyM_j7a.mjs → RequestAnatomy-CsWV3W03.mjs} +3 -3
  17. package/.output/server/_ssr/{ResponseView-iZQH7EYu.mjs → ResponseView-BuCCV3LU.mjs} +3 -3
  18. package/.output/server/_ssr/{StreamingChunkSequence-BU6hyHMg.mjs → StreamingChunkSequence-C5dE7V1d.mjs} +2 -2
  19. package/.output/server/_ssr/{index-D7QF4E6v.mjs → index-BeIi0CPv.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-DLLY6gv_.mjs → router-Ca2MCyUX.mjs} +78 -71
  22. package/.output/server/{_tanstack-start-manifest_v-CHmIZaVV.mjs → _tanstack-start-manifest_v-CBAJfSAX.mjs} +1 -1
  23. package/.output/server/index.mjs +60 -60
  24. package/package.json +1 -1
  25. package/src/components/ProxyViewer.tsx +42 -8
  26. package/src/components/proxy-viewer/ConversationHeader.tsx +10 -6
  27. package/src/proxy/formats/registry.ts +7 -1
  28. package/src/proxy/store.ts +14 -11
  29. package/.output/public/assets/_sessionId-DurHDwM1.js +0 -1
  30. package/.output/public/assets/index-sGphugFR.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, v as packageJson, h as GroupEvidenceExportResultSchema, i as DeleteInspectorGroupResponseSchema, d as RuntimeConfigSchema, P as ProviderConfigSchema, r as requestFormatForPath, l as createPendingProviderTestResults, n as ProviderTestResultsSchema, o as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, q as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, m as maskApiKey, j as providerHasContextMetadata, k as findProviderModelMetadata, g as getSessionPath, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, t as resolveProviderContextWindow, K as KnowledgeCandidateSchema, f as stripClaudeCodeBillingHeader, u as isPlainRecord, G as GroupEvidenceReadResponseSchema, I as InspectorGroupsListResponseSchema } from "./router-DLLY6gv_.mjs";
2
+ import { C as CapturedLogSchema, D as DEFAULT_CAPTURE_MODE, a as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, b as DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS, c as DEFAULT_TIME_DISPLAY_FORMAT, v as packageJson, h as GroupEvidenceExportResultSchema, i as DeleteInspectorGroupResponseSchema, d as RuntimeConfigSchema, P as ProviderConfigSchema, r as requestFormatForPath, l as createPendingProviderTestResults, n as ProviderTestResultsSchema, o as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, q as MAX_PROVIDER_TEST_TIMEOUT_SECONDS, T as TimeDisplayFormatSchema, m as maskApiKey, j as providerHasContextMetadata, k as findProviderModelMetadata, g as getSessionPath, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, e as AnthropicRequestSchema, s as safeGetOwnProperty, t as resolveProviderContextWindow, K as KnowledgeCandidateSchema, f as stripClaudeCodeBillingHeader, u as isPlainRecord, G as GroupEvidenceReadResponseSchema, I as InspectorGroupsListResponseSchema } from "./router-Ca2MCyUX.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -9,7 +9,7 @@ import { R as Root, T as Trigger$2, C as Content, a as Close, b as Title, P as P
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u as useWindowVirtualizer, a 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
- import { C as Check, X, P as Plus, D as Download, L as Layers, a as LoaderCircle, R as RefreshCw, S as Settings, A as ArrowLeft, b as Copy, c as ChevronDown, T as Trash2, U as Upload, d as Scan, e as CircleAlert, f as Terminal, g as ChevronUp, E as EyeOff, h as Eye, i as ExternalLink, j as RotateCw, k as Clock, l as ChevronRight, m as Pencil, n as ArrowUpRight, o as ArrowDownRight, p as User, M as MessageSquare, Z as Zap, W as Wrench, q as TriangleAlert, B as Brain, r as Minus, s as CircleCheckBig, t as CircleX, u as ShieldCheck, v as Save, F as FileSearch, w as CircleCheck, x as CloudUpload, O as OctagonAlert, y as Radio, G as Gauge, z as Globe, H as FileTerminal, I as ChevronsUp, J as ChevronsDown, K as FileDiff, N as History, Q as RotateCcw, V as Search, Y as GitCompareArrows, _ as CircleQuestionMark, $ as Server, a0 as Lock, a1 as Wifi, a2 as WifiOff, a3 as ArrowUp, a4 as ArrowDown, a5 as Ellipsis, a6 as CodeXml, a7 as Blocks, a8 as MousePointerClick, a9 as FileText, aa as Rows3, ab as Columns2 } from "../_libs/lucide-react.mjs";
12
+ import { C as Check, X, P as Plus, D as Download, F as FileBraces, T as Trash2, L as Layers, a as LoaderCircle, R as RefreshCw, S as Settings, A as ArrowLeft, b as Copy, c as ChevronDown, U as Upload, d as Scan, e as CircleAlert, f as Terminal, g as ChevronUp, E as EyeOff, h as Eye, i as ExternalLink, j as RotateCw, k as Clock, l as ChevronRight, m as Pencil, n as ArrowUpRight, o as ArrowDownRight, p as User, M as MessageSquare, Z as Zap, W as Wrench, q as TriangleAlert, B as Brain, r as Minus, s as CircleCheckBig, t as CircleX, u as ShieldCheck, v as Save, w as FileSearch, x as CircleCheck, y as CloudUpload, O as OctagonAlert, z as Radio, G as Gauge, H as Globe, I as FileTerminal, J as ChevronsUp, K as ChevronsDown, N as FileDiff, Q as History, V as RotateCcw, Y as Search, _ as GitCompareArrows, $ as CircleQuestionMark, a0 as Server, a1 as Lock, a2 as Wifi, a3 as WifiOff, a4 as ArrowUp, a5 as ArrowDown, a6 as Ellipsis, a7 as CodeXml, a8 as Blocks, a9 as MousePointerClick, aa as FileText, ab as Rows3, ac as Columns2 } from "../_libs/lucide-react.mjs";
13
13
  import { M as Markdown } from "../_libs/react-markdown.mjs";
14
14
  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";
15
15
  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";
@@ -700,264 +700,573 @@ function ConfirmDialog({
700
700
  ] })
701
701
  ] }) });
702
702
  }
703
- const API_FORMAT_LABELS = {
704
- anthropic: "Anthropic",
705
- openai: "OpenAI",
706
- unknown: "Unknown"
707
- };
708
- function ConversationHeader({
709
- conversationId,
710
- startTime,
711
- endTime,
712
- totalCalls,
713
- totalInputTokens,
714
- totalOutputTokens,
715
- apiFormat,
716
- expanded,
717
- onToggle,
718
- hideApiFormat = false,
719
- isLoading = false,
720
- userAgent,
721
- timeDisplayFormat,
722
- onClear
723
- }) {
724
- const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
725
- const handleClearClick = (e) => {
726
- e.stopPropagation();
727
- if (onClear === void 0) return;
728
- setConfirmOpen(true);
703
+ function forEachCodePoint(text, visit) {
704
+ for (const ch of Array.from(text)) {
705
+ visit(ch);
706
+ }
707
+ }
708
+ const CJK_REGEX = /[\u3000-\u9FFF\uAC00-\uD7FF\uF900-\uFAFF\uFF00-\uFFEF\u{1F300}-\u{1FAFF}]/u;
709
+ function estimateTokens(text) {
710
+ if (text.length === 0) return 0;
711
+ let cjk = 0;
712
+ let other = 0;
713
+ forEachCodePoint(text, (ch) => {
714
+ if (CJK_REGEX.test(ch)) {
715
+ cjk += 1;
716
+ } else {
717
+ other += 1;
718
+ }
719
+ });
720
+ return Math.ceil(cjk * 1) + Math.ceil(other / 4);
721
+ }
722
+ function countCharacters(text) {
723
+ if (text.length === 0) return 0;
724
+ let n = 0;
725
+ forEachCodePoint(text, () => {
726
+ n += 1;
727
+ });
728
+ return n;
729
+ }
730
+ function emptyRequestAnalysis(rawBody) {
731
+ return {
732
+ parsed: null,
733
+ comparisonValue: rawBody,
734
+ messageCount: null,
735
+ toolCount: null
729
736
  };
730
- const handleOpenInNewTab = reactExports.useCallback(
731
- (e) => {
732
- e.stopPropagation();
733
- window.open(getSessionPath(conversationId), "_blank", "noopener,noreferrer");
734
- },
735
- [conversationId]
736
- );
737
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
738
- "div",
739
- {
740
- role: "button",
741
- tabIndex: 0,
742
- "data-nav-id": `conv-${conversationId}`,
743
- "data-nav-action": expanded ? "collapse" : "expand",
744
- className: cn(
745
- "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
746
- "hover:bg-muted/50",
747
- "select-none",
748
- "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
749
- "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
750
- ),
751
- onClick: onToggle,
752
- onKeyDown: (e) => {
753
- if (e.target !== e.currentTarget) return;
754
- if (e.key === "Enter" || e.key === " ") {
755
- e.preventDefault();
756
- onToggle();
757
- }
758
- },
759
- children: [
760
- expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" }),
761
- /* @__PURE__ */ jsxRuntimeExports.jsx(
762
- "span",
763
- {
764
- className: "text-purple-400/90 font-mono text-xs font-semibold shrink-0",
765
- title: conversationId,
766
- children: conversationId.startsWith("PID:") || conversationId.includes("|") ? conversationId : conversationId.length > 24 ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12) : conversationId
767
- }
768
- ),
769
- userAgent !== null && userAgent !== void 0 && userAgent !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
770
- "span",
771
- {
772
- className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
773
- title: userAgent,
774
- children: [
775
- /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
776
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[120px]", children: userAgent })
777
- ]
778
- }
779
- ),
780
- !hideApiFormat && /* @__PURE__ */ jsxRuntimeExports.jsx(
781
- Badge,
782
- {
783
- variant: "outline",
784
- className: cn(
785
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
786
- apiFormat === "openai" && "border-blue-500/40 text-blue-400",
787
- apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
788
- apiFormat === "unknown" && "border-muted text-muted-foreground"
789
- ),
790
- children: API_FORMAT_LABELS[apiFormat]
791
- }
792
- ),
793
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
794
- /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
795
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatTimestampRange(startTime, endTime, timeDisplayFormat) })
796
- ] }),
797
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
798
- /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
799
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
800
- totalCalls,
801
- " call",
802
- totalCalls !== 1 ? "s" : ""
803
- ] })
804
- ] }),
805
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
806
- /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
807
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
808
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: formatTokens(totalInputTokens) }),
809
- " / ",
810
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: formatTokens(totalOutputTokens) })
811
- ] })
812
- ] }),
813
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
814
- /* @__PURE__ */ jsxRuntimeExports.jsx(
815
- "button",
816
- {
817
- type: "button",
818
- onClick: handleOpenInNewTab,
819
- onKeyDown: (e) => {
820
- if (e.key === "Enter" || e.key === " ") {
821
- handleOpenInNewTab(e);
822
- }
823
- },
824
- "aria-label": `Open session ${conversationId} in a new tab`,
825
- title: "Open this session in a new tab",
826
- className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer",
827
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3.5" })
828
- }
829
- ),
830
- onClear !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
831
- "button",
832
- {
833
- type: "button",
834
- onClick: handleClearClick,
835
- "aria-label": `Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`,
836
- title: "Clear this group",
837
- className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer",
838
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" })
839
- }
840
- ),
841
- /* @__PURE__ */ jsxRuntimeExports.jsx(
842
- ConfirmDialog,
843
- {
844
- open: confirmOpen,
845
- onOpenChange: setConfirmOpen,
846
- title: "Clear this group?",
847
- description: `This will remove ${totalCalls} request${totalCalls !== 1 ? "s" : ""} from this conversation. This action cannot be undone.`,
848
- confirmLabel: "Clear",
849
- variant: "destructive",
850
- onConfirm: () => {
851
- onClear?.();
737
+ }
738
+ const EMPTY_RESPONSE_ANALYSIS = {
739
+ parsed: null,
740
+ toolNames: null
741
+ };
742
+ function contentToText$1(content) {
743
+ if (typeof content === "string") return content;
744
+ if (!Array.isArray(content)) return "";
745
+ const parts = [];
746
+ for (const block of content) {
747
+ if (block === null || typeof block !== "object") continue;
748
+ const b = block;
749
+ const type = b.type;
750
+ switch (type) {
751
+ case "text":
752
+ if (typeof b.text === "string") parts.push(b.text);
753
+ break;
754
+ case "thinking":
755
+ case "think":
756
+ if (typeof b.thinking === "string") parts.push(b.thinking);
757
+ break;
758
+ case "tool_use": {
759
+ const name = typeof b.name === "string" ? b.name : "";
760
+ const input = b.input !== void 0 ? JSON.stringify(b.input) : "";
761
+ parts.push(`${name} ${input}`.trim());
762
+ break;
763
+ }
764
+ case "tool_result": {
765
+ const inner = b.content;
766
+ if (typeof inner === "string") {
767
+ parts.push(inner);
768
+ } else if (Array.isArray(inner)) {
769
+ for (const item of inner) {
770
+ if (item !== null && typeof item === "object") {
771
+ const t = item.text;
772
+ if (typeof t === "string") parts.push(t);
852
773
  }
853
774
  }
854
- )
855
- ]
775
+ }
776
+ break;
777
+ }
778
+ case "image":
779
+ parts.push("[image]");
780
+ break;
856
781
  }
857
- );
782
+ }
783
+ return parts.join("\n");
858
784
  }
859
- function getGroupApiFormat(logs) {
860
- const firstNonUnknown = logs.find((l) => l.apiFormat !== "unknown");
861
- if (firstNonUnknown !== void 0) {
862
- return firstNonUnknown.apiFormat;
785
+ function systemToText(system) {
786
+ if (typeof system === "string") return system;
787
+ if (!Array.isArray(system)) return "";
788
+ const parts = [];
789
+ for (const block of system) {
790
+ if (block !== null && typeof block === "object") {
791
+ const t = block.text;
792
+ const type = block.type;
793
+ if (type === "text" && typeof t === "string") parts.push(t);
794
+ }
863
795
  }
864
- return logs[0]?.apiFormat ?? "unknown";
796
+ return parts.join("\n");
865
797
  }
866
- function hasMixedApiFormat(logs) {
867
- let hasAnthropic = false;
868
- let hasOpenai = false;
869
- for (const log of logs) {
870
- if (log.apiFormat === "anthropic") hasAnthropic = true;
871
- else if (log.apiFormat === "openai") hasOpenai = true;
872
- if (hasAnthropic && hasOpenai) return true;
798
+ function toolsToText$1(tools) {
799
+ if (!Array.isArray(tools)) return "";
800
+ const parts = [];
801
+ for (const tool of tools) {
802
+ if (tool === null || typeof tool !== "object") continue;
803
+ const t = tool;
804
+ if (typeof t.name === "string") parts.push(t.name);
805
+ if (typeof t.description === "string") parts.push(t.description);
806
+ if (t.input_schema !== void 0) parts.push(JSON.stringify(t.input_schema));
873
807
  }
874
- return false;
808
+ return parts.join("\n");
875
809
  }
876
- function getConversationId(log) {
877
- if (log.isTest === true) return "provider-test";
878
- if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== void 0) {
879
- return log.sessionId;
810
+ function segment$1(role, label, text, path) {
811
+ return {
812
+ role,
813
+ label,
814
+ text,
815
+ size: estimateTokens(text),
816
+ characters: countCharacters(text),
817
+ path
818
+ };
819
+ }
820
+ const anthropicLogFormatAdapter = {
821
+ format: "anthropic",
822
+ analyzeRequest(rawBody) {
823
+ if (rawBody === null) return emptyRequestAnalysis(rawBody);
824
+ try {
825
+ const result = AnthropicRequestSchema.safeParse(JSON.parse(rawBody));
826
+ if (!result.success) return emptyRequestAnalysis(rawBody);
827
+ return {
828
+ parsed: result.data,
829
+ comparisonValue: result.data,
830
+ messageCount: result.data.messages.length,
831
+ toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
832
+ };
833
+ } catch {
834
+ return emptyRequestAnalysis(rawBody);
835
+ }
836
+ },
837
+ analyzeResponse(responseText) {
838
+ if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
839
+ try {
840
+ let json = JSON.parse(responseText);
841
+ if (typeof json === "string") json = JSON.parse(json);
842
+ const result = AnthropicResponseSchema$1.safeParse(json);
843
+ if (!result.success) return EMPTY_RESPONSE_ANALYSIS;
844
+ const toolNames = result.data.content.filter((block) => block.type === "tool_use").map((block) => block.name);
845
+ return {
846
+ parsed: result.data,
847
+ toolNames: toolNames.length > 0 ? toolNames : null
848
+ };
849
+ } catch {
850
+ return EMPTY_RESPONSE_ANALYSIS;
851
+ }
852
+ },
853
+ anatomySegments(parsed) {
854
+ if (parsed === null || typeof parsed !== "object") return null;
855
+ const body = parsed;
856
+ const segments = [];
857
+ if (body.system !== void 0) {
858
+ const text = systemToText(body.system);
859
+ if (text.length > 0) {
860
+ segments.push(segment$1("system", "system", text, "/system"));
861
+ }
862
+ }
863
+ if (Array.isArray(body.messages)) {
864
+ body.messages.forEach((message, index) => {
865
+ if (message === null || typeof message !== "object") return;
866
+ const m = message;
867
+ const role = m.role === "user" || m.role === "assistant" ? m.role : "user";
868
+ const text = contentToText$1(m.content);
869
+ segments.push(segment$1(role, `[${index}] ${role}`, text, `/messages/${index}`));
870
+ });
871
+ }
872
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
873
+ const text = toolsToText$1(body.tools);
874
+ segments.push(segment$1("tools", "tools", text, "/tools"));
875
+ }
876
+ return segments.length > 0 ? segments : null;
880
877
  }
881
- const hasPid = log.clientPid !== null && log.clientPid !== void 0;
882
- const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== void 0;
883
- if (!hasPid && !hasFolder) return "default";
884
- const pid = hasPid ? `PID:${log.clientPid}` : "unknown";
885
- const folder = hasFolder ? log.clientProjectFolder : "no-folder";
886
- return `${pid}|${folder}`;
878
+ };
879
+ function contentToText(content) {
880
+ if (typeof content === "string") return content;
881
+ if (!Array.isArray(content)) return "";
882
+ const parts = [];
883
+ for (const block of content) {
884
+ if (block === null || typeof block !== "object") continue;
885
+ const b = block;
886
+ const type = b.type;
887
+ if (type === "text" && typeof b.text === "string") {
888
+ parts.push(b.text);
889
+ } else if (type === "image_url") {
890
+ parts.push("[image]");
891
+ }
892
+ }
893
+ return parts.join("\n");
887
894
  }
888
- function groupLogsByConversation(logs) {
889
- const groups = /* @__PURE__ */ new Map();
890
- for (const log of logs) {
891
- const convId = getConversationId(log);
892
- const existing = groups.get(convId);
893
- if (existing !== void 0) {
894
- existing.push(log);
895
- } else {
896
- groups.set(convId, [log]);
895
+ function messageToText(message) {
896
+ const parts = [];
897
+ const text = contentToText(message.content);
898
+ if (text.length > 0) parts.push(text);
899
+ const reasoning = typeof message.reasoning_content === "string" ? message.reasoning_content : typeof message.thinking === "string" ? message.thinking : typeof message.think === "string" ? message.think : "";
900
+ if (reasoning.length > 0) parts.push(reasoning);
901
+ const toolCalls = message.tool_calls;
902
+ if (Array.isArray(toolCalls)) {
903
+ for (const call of toolCalls) {
904
+ if (call === null || typeof call !== "object") continue;
905
+ const fn = call.function;
906
+ if (fn === void 0) continue;
907
+ const name = typeof fn.name === "string" ? fn.name : "";
908
+ const args = typeof fn.arguments === "string" ? fn.arguments : "";
909
+ parts.push(`${name} ${args}`.trim());
897
910
  }
898
911
  }
899
- const result = [];
900
- for (const [conversationId, groupLogs] of groups) {
901
- const sorted = [...groupLogs].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
902
- result.push({
903
- id: conversationId,
904
- conversationId,
905
- logs: sorted
906
- });
912
+ return parts.join("\n");
913
+ }
914
+ function toolsToText(tools) {
915
+ if (!Array.isArray(tools)) return "";
916
+ const parts = [];
917
+ for (const tool of tools) {
918
+ if (tool === null || typeof tool !== "object") continue;
919
+ const t = tool;
920
+ const fn = t.function;
921
+ if (fn === void 0) continue;
922
+ if (typeof fn.name === "string") parts.push(fn.name);
923
+ if (typeof fn.description === "string") parts.push(fn.description);
924
+ if (fn.parameters !== void 0) parts.push(JSON.stringify(fn.parameters));
907
925
  }
908
- result.sort((a, b) => (a.logs[0]?.timestamp ?? "").localeCompare(b.logs[0]?.timestamp ?? ""));
909
- return result;
926
+ return parts.join("\n");
910
927
  }
911
- function isRecord(value) {
912
- return typeof value === "object" && value !== null && !Array.isArray(value);
928
+ function segment(role, label, text, path) {
929
+ return {
930
+ role,
931
+ label,
932
+ text,
933
+ size: estimateTokens(text),
934
+ characters: countCharacters(text),
935
+ path
936
+ };
913
937
  }
914
- function extractStopReason(log) {
915
- if (log.responseText === null) return null;
916
- try {
917
- let json = JSON.parse(log.responseText);
918
- if (typeof json === "string") {
919
- json = JSON.parse(json);
938
+ const openAILogFormatAdapter = {
939
+ format: "openai",
940
+ analyzeRequest(rawBody) {
941
+ if (rawBody === null) return emptyRequestAnalysis(rawBody);
942
+ try {
943
+ const result = OpenAIRequestSchema.safeParse(JSON.parse(rawBody));
944
+ if (!result.success) return emptyRequestAnalysis(rawBody);
945
+ return {
946
+ parsed: result.data,
947
+ comparisonValue: result.data,
948
+ messageCount: result.data.messages.length,
949
+ toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
950
+ };
951
+ } catch {
952
+ return emptyRequestAnalysis(rawBody);
920
953
  }
921
- if (!isRecord(json)) return null;
922
- if (typeof json.stop_reason === "string") {
923
- if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
924
- return json.stop_reason;
925
- }
926
- return null;
954
+ },
955
+ analyzeResponse(responseText) {
956
+ if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
957
+ const parsed = parseOpenAIResponse(responseText);
958
+ if (parsed === null) return EMPTY_RESPONSE_ANALYSIS;
959
+ const toolNames = parsed.choices[0]?.message?.tool_calls?.map((toolCall) => toolCall.function?.name ?? "").filter((name) => name !== "") ?? [];
960
+ return {
961
+ parsed,
962
+ toolNames: toolNames.length > 0 ? toolNames : null
963
+ };
964
+ },
965
+ anatomySegments(parsed) {
966
+ if (parsed === null || typeof parsed !== "object") return null;
967
+ const body = parsed;
968
+ const segments = [];
969
+ if (Array.isArray(body.messages)) {
970
+ body.messages.forEach((message, index) => {
971
+ if (message === null || typeof message !== "object") return;
972
+ const m = message;
973
+ const role = m.role === "user" || m.role === "assistant" || m.role === "system" || m.role === "tool" ? m.role : "user";
974
+ const text = messageToText(m);
975
+ segments.push(segment(role, `[${index}] ${role}`, text, `/messages/${index}`));
976
+ });
927
977
  }
928
- 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") {
929
- return "stop";
978
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
979
+ const text = toolsToText(body.tools);
980
+ segments.push(segment("tools", "tools", text, "/tools"));
930
981
  }
931
- return null;
932
- } catch {
982
+ return segments.length > 0 ? segments : null;
983
+ }
984
+ };
985
+ const unknownLogFormatAdapter = {
986
+ format: "unknown",
987
+ analyzeRequest(rawBody) {
988
+ return emptyRequestAnalysis(rawBody);
989
+ },
990
+ analyzeResponse() {
991
+ return EMPTY_RESPONSE_ANALYSIS;
992
+ },
993
+ anatomySegments() {
933
994
  return null;
934
995
  }
996
+ };
997
+ const ADAPTERS = {
998
+ anthropic: anthropicLogFormatAdapter,
999
+ openai: openAILogFormatAdapter,
1000
+ unknown: unknownLogFormatAdapter
1001
+ };
1002
+ function getLogFormatAdapter(format) {
1003
+ return ADAPTERS[format];
935
1004
  }
936
- function isTurnBoundary(stopReason) {
937
- return stopReason === "end_turn" || stopReason === "stop";
1005
+ function resolveLogFormat(log) {
1006
+ const pathFormat = requestFormatForPath(log.path);
1007
+ return pathFormat === "unknown" ? log.apiFormat : pathFormat;
938
1008
  }
939
- function SvgShell({
940
- className,
941
- style,
942
- d,
943
- eyeStalks,
944
- eyes,
945
- legs,
946
- extras
947
- }) {
948
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
949
- "svg",
950
- {
951
- viewBox: "0 0 24 24",
952
- fill: "none",
953
- stroke: "currentColor",
954
- strokeWidth: "1.5",
955
- strokeLinecap: "round",
956
- strokeLinejoin: "round",
957
- "aria-hidden": "true",
958
- style,
959
- className: cn("inline-block size-5", className),
960
- children: [
1009
+ const API_FORMAT_LABELS = {
1010
+ anthropic: "Anthropic",
1011
+ openai: "OpenAI",
1012
+ unknown: "Unknown"
1013
+ };
1014
+ function ConversationHeader({
1015
+ conversationId,
1016
+ startTime,
1017
+ endTime,
1018
+ totalCalls,
1019
+ totalInputTokens,
1020
+ totalOutputTokens,
1021
+ apiFormat,
1022
+ expanded,
1023
+ onToggle,
1024
+ hideApiFormat = false,
1025
+ isLoading = false,
1026
+ userAgent,
1027
+ timeDisplayFormat,
1028
+ onClear
1029
+ }) {
1030
+ const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
1031
+ const handleClearClick = (e) => {
1032
+ e.stopPropagation();
1033
+ if (onClear === void 0) return;
1034
+ setConfirmOpen(true);
1035
+ };
1036
+ const handleOpenInNewTab = reactExports.useCallback(
1037
+ (e) => {
1038
+ e.stopPropagation();
1039
+ window.open(getSessionPath(conversationId), "_blank", "noopener,noreferrer");
1040
+ },
1041
+ [conversationId]
1042
+ );
1043
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1044
+ "div",
1045
+ {
1046
+ role: "button",
1047
+ tabIndex: 0,
1048
+ "data-nav-id": `conv-${conversationId}`,
1049
+ "data-nav-action": expanded ? "collapse" : "expand",
1050
+ className: cn(
1051
+ "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
1052
+ "hover:bg-muted/50",
1053
+ "select-none",
1054
+ "border border-border rounded-lg mb-2 bg-background sticky top-0 z-10",
1055
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
1056
+ ),
1057
+ onClick: onToggle,
1058
+ onKeyDown: (e) => {
1059
+ if (e.target !== e.currentTarget) return;
1060
+ if (e.key === "Enter" || e.key === " ") {
1061
+ e.preventDefault();
1062
+ onToggle();
1063
+ }
1064
+ },
1065
+ children: [
1066
+ expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-4 text-muted-foreground shrink-0" }) : isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground shrink-0" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-4 text-muted-foreground shrink-0" }),
1067
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1068
+ "span",
1069
+ {
1070
+ className: "text-purple-400/90 font-mono text-xs font-semibold shrink-0",
1071
+ title: conversationId,
1072
+ children: conversationId.startsWith("PID:") || conversationId.includes("|") ? conversationId : conversationId.length > 24 ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12) : conversationId
1073
+ }
1074
+ ),
1075
+ userAgent !== null && userAgent !== void 0 && userAgent !== "" && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1076
+ "span",
1077
+ {
1078
+ className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0",
1079
+ title: userAgent,
1080
+ children: [
1081
+ /* @__PURE__ */ jsxRuntimeExports.jsx(User, { className: "size-3" }),
1082
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[120px]", children: userAgent })
1083
+ ]
1084
+ }
1085
+ ),
1086
+ !hideApiFormat && /* @__PURE__ */ jsxRuntimeExports.jsx(
1087
+ Badge,
1088
+ {
1089
+ variant: "outline",
1090
+ className: cn(
1091
+ "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
1092
+ apiFormat === "openai" && "border-blue-500/40 text-blue-400",
1093
+ apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
1094
+ apiFormat === "unknown" && "border-muted text-muted-foreground"
1095
+ ),
1096
+ children: API_FORMAT_LABELS[apiFormat]
1097
+ }
1098
+ ),
1099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1100
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1101
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatTimestampRange(startTime, endTime, timeDisplayFormat) })
1102
+ ] }),
1103
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1104
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
1105
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
1106
+ totalCalls,
1107
+ " call",
1108
+ totalCalls !== 1 ? "s" : ""
1109
+ ] })
1110
+ ] }),
1111
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1112
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
1113
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
1114
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: formatTokens(totalInputTokens) }),
1115
+ " / ",
1116
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: formatTokens(totalOutputTokens) })
1117
+ ] })
1118
+ ] }),
1119
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0" }),
1120
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1121
+ "button",
1122
+ {
1123
+ type: "button",
1124
+ onClick: handleOpenInNewTab,
1125
+ onKeyDown: (e) => {
1126
+ if (e.key === "Enter" || e.key === " ") {
1127
+ handleOpenInNewTab(e);
1128
+ }
1129
+ },
1130
+ "aria-label": `Open session ${conversationId} in a new tab`,
1131
+ title: "Open this session in a new tab",
1132
+ className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer",
1133
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3.5" })
1134
+ }
1135
+ ),
1136
+ onClear !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
1137
+ "button",
1138
+ {
1139
+ type: "button",
1140
+ onClick: handleClearClick,
1141
+ "aria-label": `Clear group (${totalCalls} request${totalCalls !== 1 ? "s" : ""})`,
1142
+ title: "Clear this group",
1143
+ className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 inline-flex items-center justify-center size-8 rounded hover:bg-muted cursor-pointer",
1144
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" })
1145
+ }
1146
+ ),
1147
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
1148
+ ConfirmDialog,
1149
+ {
1150
+ open: confirmOpen,
1151
+ onOpenChange: setConfirmOpen,
1152
+ title: "Clear this group?",
1153
+ description: `This will remove ${totalCalls} request${totalCalls !== 1 ? "s" : ""} from this conversation. This action cannot be undone.`,
1154
+ confirmLabel: "Clear",
1155
+ variant: "destructive",
1156
+ onConfirm: () => {
1157
+ onClear?.();
1158
+ }
1159
+ }
1160
+ )
1161
+ ]
1162
+ }
1163
+ );
1164
+ }
1165
+ function getGroupApiFormat(logs) {
1166
+ for (const log of logs) {
1167
+ const apiFormat = resolveLogFormat(log);
1168
+ if (apiFormat !== "unknown") {
1169
+ return apiFormat;
1170
+ }
1171
+ }
1172
+ return "unknown";
1173
+ }
1174
+ function hasMixedApiFormat(logs) {
1175
+ let hasAnthropic = false;
1176
+ let hasOpenai = false;
1177
+ for (const log of logs) {
1178
+ const apiFormat = resolveLogFormat(log);
1179
+ if (apiFormat === "anthropic") hasAnthropic = true;
1180
+ else if (apiFormat === "openai") hasOpenai = true;
1181
+ if (hasAnthropic && hasOpenai) return true;
1182
+ }
1183
+ return false;
1184
+ }
1185
+ function getConversationId(log) {
1186
+ if (log.isTest === true) return "provider-test";
1187
+ if (log.sessionId !== null && log.sessionId !== "" && log.sessionId !== void 0) {
1188
+ return log.sessionId;
1189
+ }
1190
+ const hasPid = log.clientPid !== null && log.clientPid !== void 0;
1191
+ const hasFolder = log.clientProjectFolder !== null && log.clientProjectFolder !== void 0;
1192
+ if (!hasPid && !hasFolder) return "default";
1193
+ const pid = hasPid ? `PID:${log.clientPid}` : "unknown";
1194
+ const folder = hasFolder ? log.clientProjectFolder : "no-folder";
1195
+ return `${pid}|${folder}`;
1196
+ }
1197
+ function groupLogsByConversation(logs) {
1198
+ const groups = /* @__PURE__ */ new Map();
1199
+ for (const log of logs) {
1200
+ const convId = getConversationId(log);
1201
+ const existing = groups.get(convId);
1202
+ if (existing !== void 0) {
1203
+ existing.push(log);
1204
+ } else {
1205
+ groups.set(convId, [log]);
1206
+ }
1207
+ }
1208
+ const result = [];
1209
+ for (const [conversationId, groupLogs] of groups) {
1210
+ const sorted = [...groupLogs].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1211
+ result.push({
1212
+ id: conversationId,
1213
+ conversationId,
1214
+ logs: sorted
1215
+ });
1216
+ }
1217
+ result.sort((a, b) => (a.logs[0]?.timestamp ?? "").localeCompare(b.logs[0]?.timestamp ?? ""));
1218
+ return result;
1219
+ }
1220
+ function isRecord(value) {
1221
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1222
+ }
1223
+ function extractStopReason(log) {
1224
+ if (log.responseText === null) return null;
1225
+ try {
1226
+ let json = JSON.parse(log.responseText);
1227
+ if (typeof json === "string") {
1228
+ json = JSON.parse(json);
1229
+ }
1230
+ if (!isRecord(json)) return null;
1231
+ if (typeof json.stop_reason === "string") {
1232
+ if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
1233
+ return json.stop_reason;
1234
+ }
1235
+ return null;
1236
+ }
1237
+ 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") {
1238
+ return "stop";
1239
+ }
1240
+ return null;
1241
+ } catch {
1242
+ return null;
1243
+ }
1244
+ }
1245
+ function isTurnBoundary(stopReason) {
1246
+ return stopReason === "end_turn" || stopReason === "stop";
1247
+ }
1248
+ function SvgShell({
1249
+ className,
1250
+ style,
1251
+ d,
1252
+ eyeStalks,
1253
+ eyes,
1254
+ legs,
1255
+ extras
1256
+ }) {
1257
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1258
+ "svg",
1259
+ {
1260
+ viewBox: "0 0 24 24",
1261
+ fill: "none",
1262
+ stroke: "currentColor",
1263
+ strokeWidth: "1.5",
1264
+ strokeLinecap: "round",
1265
+ strokeLinejoin: "round",
1266
+ "aria-hidden": "true",
1267
+ style,
1268
+ className: cn("inline-block size-5", className),
1269
+ children: [
961
1270
  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d }),
962
1271
  eyeStalks,
963
1272
  eyes,
@@ -1572,19 +1881,19 @@ function useCopyFeedback(text) {
1572
1881
  return { copied, copy };
1573
1882
  }
1574
1883
  const LazyCompareDrawer = reactExports.lazy(
1575
- () => import("./CompareDrawer-BbJ5UdYD.mjs").then((m) => ({ default: m.CompareDrawer }))
1884
+ () => import("./CompareDrawer-Ce2590rK.mjs").then((m) => ({ default: m.CompareDrawer }))
1576
1885
  );
1577
1886
  const LazyReplayDialog = reactExports.lazy(
1578
- () => import("./ReplayDialog-CgWweshq.mjs").then((m) => ({ default: m.ReplayDialog }))
1887
+ () => import("./ReplayDialog-CB4KOlLe.mjs").then((m) => ({ default: m.ReplayDialog }))
1579
1888
  );
1580
1889
  const LazyRequestAnatomy = reactExports.lazy(
1581
- () => import("./RequestAnatomy-BAyM_j7a.mjs").then((m) => ({ default: m.RequestAnatomy }))
1890
+ () => import("./RequestAnatomy-CsWV3W03.mjs").then((m) => ({ default: m.RequestAnatomy }))
1582
1891
  );
1583
1892
  const LazyResponseView = reactExports.lazy(
1584
- () => import("./ResponseView-iZQH7EYu.mjs").then((m) => ({ default: m.ResponseView }))
1893
+ () => import("./ResponseView-BuCCV3LU.mjs").then((m) => ({ default: m.ResponseView }))
1585
1894
  );
1586
1895
  const LazyStreamingChunkSequence = reactExports.lazy(
1587
- () => import("./StreamingChunkSequence-BU6hyHMg.mjs").then((m) => ({
1896
+ () => import("./StreamingChunkSequence-C5dE7V1d.mjs").then((m) => ({
1588
1897
  default: m.StreamingChunkSequence
1589
1898
  }))
1590
1899
  );
@@ -2688,542 +2997,236 @@ function ScrollBar({
2688
2997
  "flex touch-none p-px transition-colors select-none",
2689
2998
  orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
2690
2999
  orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
2691
- className
2692
- ),
2693
- ...props,
2694
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2695
- ScrollAreaThumb,
2696
- {
2697
- "data-slot": "scroll-area-thumb",
2698
- className: "bg-border relative flex-1 rounded-full"
2699
- }
2700
- )
2701
- }
2702
- );
2703
- }
2704
- const CATEGORY_LABELS = {
2705
- file: "file",
2706
- shell: "shell",
2707
- browser: "browser",
2708
- web: "web",
2709
- mcp: "MCP",
2710
- code: "code",
2711
- other: "other"
2712
- };
2713
- const CATEGORY_CLASSES = {
2714
- file: "border-blue-400/20 bg-blue-400/5 text-blue-300",
2715
- shell: "border-teal-400/20 bg-teal-400/5 text-teal-300",
2716
- browser: "border-cyan-400/20 bg-cyan-400/5 text-cyan-300",
2717
- web: "border-sky-400/20 bg-sky-400/5 text-sky-300",
2718
- mcp: "border-violet-400/20 bg-violet-400/5 text-violet-300",
2719
- code: "border-emerald-400/20 bg-emerald-400/5 text-emerald-300",
2720
- other: "border-muted-foreground/20 bg-muted/30 text-muted-foreground"
2721
- };
2722
- function CategoryIcon({ category }) {
2723
- switch (category) {
2724
- case "file":
2725
- return /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "size-3.5" });
2726
- case "shell":
2727
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5" });
2728
- case "browser":
2729
- return /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointerClick, { className: "size-3.5" });
2730
- case "web":
2731
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { className: "size-3.5" });
2732
- case "mcp":
2733
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Blocks, { className: "size-3.5" });
2734
- case "code":
2735
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { className: "size-3.5" });
2736
- case "other":
2737
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Ellipsis, { className: "size-3.5" });
2738
- }
2739
- }
2740
- function RequiredParameters({ names }) {
2741
- if (names.length === 0) return null;
2742
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1", children: [
2743
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "required" }),
2744
- names.slice(0, 4).map((name) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2745
- Badge,
2746
- {
2747
- variant: "outline",
2748
- className: "h-5 max-w-36 truncate rounded-md px-1.5 py-0 font-mono text-[10px]",
2749
- children: name
2750
- },
2751
- name
2752
- )),
2753
- names.length > 4 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
2754
- "+",
2755
- names.length - 4
2756
- ] })
2757
- ] });
2758
- }
2759
- function ToolRow({ tool, called }) {
2760
- const [open, setOpen] = reactExports.useState(false);
2761
- const nameCopy = useCopyFeedback(tool.name);
2762
- const hasDetails = tool.description !== null || tool.requiredParameters.length > 0 || tool.schema !== null;
2763
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border/60 bg-background/40", children: [
2764
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2 px-2.5 py-2", children: [
2765
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2766
- "button",
2767
- {
2768
- type: "button",
2769
- className: "flex min-w-0 flex-1 items-center gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2770
- onClick: () => setOpen((value) => !value),
2771
- "aria-expanded": open,
2772
- disabled: !hasDetails,
2773
- children: [
2774
- /* @__PURE__ */ jsxRuntimeExports.jsx(CategoryIcon, { category: tool.category }),
2775
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-xs font-semibold text-foreground/90", children: tool.name }),
2776
- called && /* @__PURE__ */ jsxRuntimeExports.jsx(
2777
- Badge,
2778
- {
2779
- variant: "outline",
2780
- className: "h-5 rounded-md border-emerald-400/20 bg-emerald-400/5 px-1.5 py-0 text-[10px] text-emerald-300",
2781
- children: "called"
2782
- }
2783
- ),
2784
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
2785
- tool.parameterCount,
2786
- " params"
2787
- ] }),
2788
- tool.descriptionPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden min-w-0 truncate text-xs text-muted-foreground md:inline", children: tool.descriptionPreview }),
2789
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
2790
- hasDetails && (open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5 shrink-0 text-muted-foreground" }))
2791
- ]
2792
- }
2793
- ),
2794
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2795
- "button",
2796
- {
2797
- type: "button",
2798
- className: "inline-flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2799
- onClick: nameCopy.copy,
2800
- "aria-label": nameCopy.copied ? "Copied tool name" : "Copy tool name",
2801
- children: nameCopy.copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
2802
- }
2803
- )
2804
- ] }),
2805
- /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 border-t border-border/60 px-2.5 py-2", children: [
2806
- tool.description !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: tool.description }),
2807
- /* @__PURE__ */ jsxRuntimeExports.jsx(RequiredParameters, { names: tool.requiredParameters }),
2808
- tool.schema !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-64 rounded-md border border-border/60 bg-muted/20 p-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(tool.schema), defaultExpandDepth: 0 }) })
2809
- ] }) })
2810
- ] }) });
2811
- }
2812
- function matchesQuery(tool, query) {
2813
- if (query.length === 0) return true;
2814
- const haystack = `${tool.name} ${tool.description ?? ""} ${CATEGORY_LABELS[tool.category]}`.toLowerCase().trim();
2815
- return haystack.includes(query);
2816
- }
2817
- const RequestTools = reactExports.memo(function RequestTools2({
2818
- summary,
2819
- calledToolNames
2820
- }) {
2821
- const [expanded, setExpanded] = reactExports.useState(false);
2822
- const [query, setQuery] = reactExports.useState("");
2823
- const calledNames = reactExports.useMemo(() => new Set(calledToolNames), [calledToolNames]);
2824
- const normalizedQuery = query.trim().toLowerCase();
2825
- const visibleTools = reactExports.useMemo(
2826
- () => summary === null ? [] : summary.tools.filter((tool) => matchesQuery(tool, normalizedQuery)),
2827
- [normalizedQuery, summary]
2828
- );
2829
- if (summary === null) return null;
2830
- const previewTools = summary.tools.slice(0, 6);
2831
- const hiddenPreviewCount = summary.tools.length - previewTools.length;
2832
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-3 rounded-lg border border-border/70 bg-muted/20", children: [
2833
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2 px-3 py-2", children: [
2834
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
2835
- /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-4 shrink-0 text-sky-300/80" }),
2836
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
2837
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [
2838
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-semibold text-foreground", children: "Request Tools" }),
2839
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 rounded-md px-1.5 py-0 font-mono text-[10px]", children: summary.tools.length }),
2840
- summary.toolChoiceLabel !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
2841
- Badge,
2842
- {
2843
- variant: "outline",
2844
- className: "h-5 rounded-md px-1.5 py-0 font-mono text-[10px] text-muted-foreground",
2845
- children: [
2846
- "tool_choice: ",
2847
- summary.toolChoiceLabel
2848
- ]
2849
- }
2850
- )
2851
- ] }),
2852
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex min-w-0 flex-wrap gap-1", children: summary.categories.map((category) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
2853
- Badge,
2854
- {
2855
- variant: "outline",
2856
- className: cn(
2857
- "h-5 rounded-md px-1.5 py-0 font-mono text-[10px]",
2858
- CATEGORY_CLASSES[category.category]
2859
- ),
2860
- children: [
2861
- category.count,
2862
- " ",
2863
- CATEGORY_LABELS[category.category]
2864
- ]
2865
- },
2866
- category.category
2867
- )) })
2868
- ] })
2869
- ] }),
2870
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
2871
- Button,
2872
- {
2873
- type: "button",
2874
- variant: "ghost",
2875
- size: "sm",
2876
- className: "h-7 px-2",
2877
- onClick: () => setExpanded((value) => !value),
2878
- "aria-expanded": expanded,
2879
- children: [
2880
- expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5" }),
2881
- expanded ? "Hide" : "Show"
2882
- ]
2883
- }
2884
- )
2885
- ] }),
2886
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap gap-1.5 border-t border-border/60 px-3 py-2", children: [
2887
- previewTools.map((tool) => /* @__PURE__ */ jsxRuntimeExports.jsx(
2888
- Badge,
2889
- {
2890
- variant: "outline",
2891
- className: cn(
2892
- "h-5 max-w-44 truncate rounded-md px-1.5 py-0 font-mono text-[10px]",
2893
- calledNames.has(tool.name) && "border-emerald-400/20 text-emerald-300"
2894
- ),
2895
- children: tool.name
2896
- },
2897
- tool.name
2898
- )),
2899
- hiddenPreviewCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] leading-5 text-muted-foreground", children: [
2900
- "+",
2901
- hiddenPreviewCount
2902
- ] })
2903
- ] }),
2904
- expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 border-t border-border/60 px-3 py-3", children: [
2905
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex h-8 items-center gap-2 rounded-md border border-border/70 bg-background/50 px-2 text-xs", children: [
2906
- /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "size-3.5 shrink-0 text-muted-foreground" }),
2907
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2908
- "input",
2909
- {
2910
- value: query,
2911
- onChange: (event) => setQuery(event.target.value),
2912
- placeholder: "Filter tools",
2913
- className: "min-w-0 flex-1 bg-transparent font-mono text-xs outline-none placeholder:text-muted-foreground/70"
2914
- }
2915
- )
2916
- ] }),
2917
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-1.5", children: visibleTools.length > 0 ? visibleTools.map((tool) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolRow, { tool, called: calledNames.has(tool.name) }, tool.name)) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "py-2 text-xs text-muted-foreground", children: "No matching tools" }) })
2918
- ] })
2919
- ] });
2920
- });
2921
- function forEachCodePoint(text, visit) {
2922
- for (const ch of Array.from(text)) {
2923
- visit(ch);
2924
- }
2925
- }
2926
- const CJK_REGEX = /[\u3000-\u9FFF\uAC00-\uD7FF\uF900-\uFAFF\uFF00-\uFFEF\u{1F300}-\u{1FAFF}]/u;
2927
- function estimateTokens(text) {
2928
- if (text.length === 0) return 0;
2929
- let cjk = 0;
2930
- let other = 0;
2931
- forEachCodePoint(text, (ch) => {
2932
- if (CJK_REGEX.test(ch)) {
2933
- cjk += 1;
2934
- } else {
2935
- other += 1;
2936
- }
2937
- });
2938
- return Math.ceil(cjk * 1) + Math.ceil(other / 4);
2939
- }
2940
- function countCharacters(text) {
2941
- if (text.length === 0) return 0;
2942
- let n = 0;
2943
- forEachCodePoint(text, () => {
2944
- n += 1;
2945
- });
2946
- return n;
2947
- }
2948
- function emptyRequestAnalysis(rawBody) {
2949
- return {
2950
- parsed: null,
2951
- comparisonValue: rawBody,
2952
- messageCount: null,
2953
- toolCount: null
2954
- };
2955
- }
2956
- const EMPTY_RESPONSE_ANALYSIS = {
2957
- parsed: null,
2958
- toolNames: null
2959
- };
2960
- function contentToText$1(content) {
2961
- if (typeof content === "string") return content;
2962
- if (!Array.isArray(content)) return "";
2963
- const parts = [];
2964
- for (const block of content) {
2965
- if (block === null || typeof block !== "object") continue;
2966
- const b = block;
2967
- const type = b.type;
2968
- switch (type) {
2969
- case "text":
2970
- if (typeof b.text === "string") parts.push(b.text);
2971
- break;
2972
- case "thinking":
2973
- case "think":
2974
- if (typeof b.thinking === "string") parts.push(b.thinking);
2975
- break;
2976
- case "tool_use": {
2977
- const name = typeof b.name === "string" ? b.name : "";
2978
- const input = b.input !== void 0 ? JSON.stringify(b.input) : "";
2979
- parts.push(`${name} ${input}`.trim());
2980
- break;
2981
- }
2982
- case "tool_result": {
2983
- const inner = b.content;
2984
- if (typeof inner === "string") {
2985
- parts.push(inner);
2986
- } else if (Array.isArray(inner)) {
2987
- for (const item of inner) {
2988
- if (item !== null && typeof item === "object") {
2989
- const t = item.text;
2990
- if (typeof t === "string") parts.push(t);
2991
- }
2992
- }
2993
- }
2994
- break;
2995
- }
2996
- case "image":
2997
- parts.push("[image]");
2998
- break;
2999
- }
3000
- }
3001
- return parts.join("\n");
3002
- }
3003
- function systemToText(system) {
3004
- if (typeof system === "string") return system;
3005
- if (!Array.isArray(system)) return "";
3006
- const parts = [];
3007
- for (const block of system) {
3008
- if (block !== null && typeof block === "object") {
3009
- const t = block.text;
3010
- const type = block.type;
3011
- if (type === "text" && typeof t === "string") parts.push(t);
3012
- }
3013
- }
3014
- return parts.join("\n");
3015
- }
3016
- function toolsToText$1(tools) {
3017
- if (!Array.isArray(tools)) return "";
3018
- const parts = [];
3019
- for (const tool of tools) {
3020
- if (tool === null || typeof tool !== "object") continue;
3021
- const t = tool;
3022
- if (typeof t.name === "string") parts.push(t.name);
3023
- if (typeof t.description === "string") parts.push(t.description);
3024
- if (t.input_schema !== void 0) parts.push(JSON.stringify(t.input_schema));
3025
- }
3026
- return parts.join("\n");
3027
- }
3028
- function segment$1(role, label, text, path) {
3029
- return {
3030
- role,
3031
- label,
3032
- text,
3033
- size: estimateTokens(text),
3034
- characters: countCharacters(text),
3035
- path
3036
- };
3037
- }
3038
- const anthropicLogFormatAdapter = {
3039
- format: "anthropic",
3040
- analyzeRequest(rawBody) {
3041
- if (rawBody === null) return emptyRequestAnalysis(rawBody);
3042
- try {
3043
- const result = AnthropicRequestSchema.safeParse(JSON.parse(rawBody));
3044
- if (!result.success) return emptyRequestAnalysis(rawBody);
3045
- return {
3046
- parsed: result.data,
3047
- comparisonValue: result.data,
3048
- messageCount: result.data.messages.length,
3049
- toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
3050
- };
3051
- } catch {
3052
- return emptyRequestAnalysis(rawBody);
3053
- }
3054
- },
3055
- analyzeResponse(responseText) {
3056
- if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
3057
- try {
3058
- let json = JSON.parse(responseText);
3059
- if (typeof json === "string") json = JSON.parse(json);
3060
- const result = AnthropicResponseSchema$1.safeParse(json);
3061
- if (!result.success) return EMPTY_RESPONSE_ANALYSIS;
3062
- const toolNames = result.data.content.filter((block) => block.type === "tool_use").map((block) => block.name);
3063
- return {
3064
- parsed: result.data,
3065
- toolNames: toolNames.length > 0 ? toolNames : null
3066
- };
3067
- } catch {
3068
- return EMPTY_RESPONSE_ANALYSIS;
3069
- }
3070
- },
3071
- anatomySegments(parsed) {
3072
- if (parsed === null || typeof parsed !== "object") return null;
3073
- const body = parsed;
3074
- const segments = [];
3075
- if (body.system !== void 0) {
3076
- const text = systemToText(body.system);
3077
- if (text.length > 0) {
3078
- segments.push(segment$1("system", "system", text, "/system"));
3079
- }
3080
- }
3081
- if (Array.isArray(body.messages)) {
3082
- body.messages.forEach((message, index) => {
3083
- if (message === null || typeof message !== "object") return;
3084
- const m = message;
3085
- const role = m.role === "user" || m.role === "assistant" ? m.role : "user";
3086
- const text = contentToText$1(m.content);
3087
- segments.push(segment$1(role, `[${index}] ${role}`, text, `/messages/${index}`));
3088
- });
3089
- }
3090
- if (Array.isArray(body.tools) && body.tools.length > 0) {
3091
- const text = toolsToText$1(body.tools);
3092
- segments.push(segment$1("tools", "tools", text, "/tools"));
3093
- }
3094
- return segments.length > 0 ? segments : null;
3095
- }
3096
- };
3097
- function contentToText(content) {
3098
- if (typeof content === "string") return content;
3099
- if (!Array.isArray(content)) return "";
3100
- const parts = [];
3101
- for (const block of content) {
3102
- if (block === null || typeof block !== "object") continue;
3103
- const b = block;
3104
- const type = b.type;
3105
- if (type === "text" && typeof b.text === "string") {
3106
- parts.push(b.text);
3107
- } else if (type === "image_url") {
3108
- parts.push("[image]");
3109
- }
3110
- }
3111
- return parts.join("\n");
3112
- }
3113
- function messageToText(message) {
3114
- const parts = [];
3115
- const text = contentToText(message.content);
3116
- if (text.length > 0) parts.push(text);
3117
- const reasoning = typeof message.reasoning_content === "string" ? message.reasoning_content : typeof message.thinking === "string" ? message.thinking : typeof message.think === "string" ? message.think : "";
3118
- if (reasoning.length > 0) parts.push(reasoning);
3119
- const toolCalls = message.tool_calls;
3120
- if (Array.isArray(toolCalls)) {
3121
- for (const call of toolCalls) {
3122
- if (call === null || typeof call !== "object") continue;
3123
- const fn = call.function;
3124
- if (fn === void 0) continue;
3125
- const name = typeof fn.name === "string" ? fn.name : "";
3126
- const args = typeof fn.arguments === "string" ? fn.arguments : "";
3127
- parts.push(`${name} ${args}`.trim());
3128
- }
3129
- }
3130
- return parts.join("\n");
3131
- }
3132
- function toolsToText(tools) {
3133
- if (!Array.isArray(tools)) return "";
3134
- const parts = [];
3135
- for (const tool of tools) {
3136
- if (tool === null || typeof tool !== "object") continue;
3137
- const t = tool;
3138
- const fn = t.function;
3139
- if (fn === void 0) continue;
3140
- if (typeof fn.name === "string") parts.push(fn.name);
3141
- if (typeof fn.description === "string") parts.push(fn.description);
3142
- if (fn.parameters !== void 0) parts.push(JSON.stringify(fn.parameters));
3143
- }
3144
- return parts.join("\n");
3145
- }
3146
- function segment(role, label, text, path) {
3147
- return {
3148
- role,
3149
- label,
3150
- text,
3151
- size: estimateTokens(text),
3152
- characters: countCharacters(text),
3153
- path
3154
- };
3155
- }
3156
- const openAILogFormatAdapter = {
3157
- format: "openai",
3158
- analyzeRequest(rawBody) {
3159
- if (rawBody === null) return emptyRequestAnalysis(rawBody);
3160
- try {
3161
- const result = OpenAIRequestSchema.safeParse(JSON.parse(rawBody));
3162
- if (!result.success) return emptyRequestAnalysis(rawBody);
3163
- return {
3164
- parsed: result.data,
3165
- comparisonValue: result.data,
3166
- messageCount: result.data.messages.length,
3167
- toolCount: result.data.tools !== void 0 && result.data.tools.length > 0 ? result.data.tools.length : null
3168
- };
3169
- } catch {
3170
- return emptyRequestAnalysis(rawBody);
3171
- }
3172
- },
3173
- analyzeResponse(responseText) {
3174
- if (responseText === null) return EMPTY_RESPONSE_ANALYSIS;
3175
- const parsed = parseOpenAIResponse(responseText);
3176
- if (parsed === null) return EMPTY_RESPONSE_ANALYSIS;
3177
- const toolNames = parsed.choices[0]?.message?.tool_calls?.map((toolCall) => toolCall.function?.name ?? "").filter((name) => name !== "") ?? [];
3178
- return {
3179
- parsed,
3180
- toolNames: toolNames.length > 0 ? toolNames : null
3181
- };
3182
- },
3183
- anatomySegments(parsed) {
3184
- if (parsed === null || typeof parsed !== "object") return null;
3185
- const body = parsed;
3186
- const segments = [];
3187
- if (Array.isArray(body.messages)) {
3188
- body.messages.forEach((message, index) => {
3189
- if (message === null || typeof message !== "object") return;
3190
- const m = message;
3191
- const role = m.role === "user" || m.role === "assistant" || m.role === "system" || m.role === "tool" ? m.role : "user";
3192
- const text = messageToText(m);
3193
- segments.push(segment(role, `[${index}] ${role}`, text, `/messages/${index}`));
3194
- });
3195
- }
3196
- if (Array.isArray(body.tools) && body.tools.length > 0) {
3197
- const text = toolsToText(body.tools);
3198
- segments.push(segment("tools", "tools", text, "/tools"));
3000
+ className
3001
+ ),
3002
+ ...props,
3003
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3004
+ ScrollAreaThumb,
3005
+ {
3006
+ "data-slot": "scroll-area-thumb",
3007
+ className: "bg-border relative flex-1 rounded-full"
3008
+ }
3009
+ )
3199
3010
  }
3200
- return segments.length > 0 ? segments : null;
3201
- }
3202
- };
3203
- const unknownLogFormatAdapter = {
3204
- format: "unknown",
3205
- analyzeRequest(rawBody) {
3206
- return emptyRequestAnalysis(rawBody);
3207
- },
3208
- analyzeResponse() {
3209
- return EMPTY_RESPONSE_ANALYSIS;
3210
- },
3211
- anatomySegments() {
3212
- return null;
3213
- }
3011
+ );
3012
+ }
3013
+ const CATEGORY_LABELS = {
3014
+ file: "file",
3015
+ shell: "shell",
3016
+ browser: "browser",
3017
+ web: "web",
3018
+ mcp: "MCP",
3019
+ code: "code",
3020
+ other: "other"
3214
3021
  };
3215
- const ADAPTERS = {
3216
- anthropic: anthropicLogFormatAdapter,
3217
- openai: openAILogFormatAdapter,
3218
- unknown: unknownLogFormatAdapter
3022
+ const CATEGORY_CLASSES = {
3023
+ file: "border-blue-400/20 bg-blue-400/5 text-blue-300",
3024
+ shell: "border-teal-400/20 bg-teal-400/5 text-teal-300",
3025
+ browser: "border-cyan-400/20 bg-cyan-400/5 text-cyan-300",
3026
+ web: "border-sky-400/20 bg-sky-400/5 text-sky-300",
3027
+ mcp: "border-violet-400/20 bg-violet-400/5 text-violet-300",
3028
+ code: "border-emerald-400/20 bg-emerald-400/5 text-emerald-300",
3029
+ other: "border-muted-foreground/20 bg-muted/30 text-muted-foreground"
3219
3030
  };
3220
- function getLogFormatAdapter(format) {
3221
- return ADAPTERS[format];
3031
+ function CategoryIcon({ category }) {
3032
+ switch (category) {
3033
+ case "file":
3034
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "size-3.5" });
3035
+ case "shell":
3036
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-3.5" });
3037
+ case "browser":
3038
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(MousePointerClick, { className: "size-3.5" });
3039
+ case "web":
3040
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { className: "size-3.5" });
3041
+ case "mcp":
3042
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Blocks, { className: "size-3.5" });
3043
+ case "code":
3044
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CodeXml, { className: "size-3.5" });
3045
+ case "other":
3046
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Ellipsis, { className: "size-3.5" });
3047
+ }
3222
3048
  }
3223
- function resolveLogFormat(log) {
3224
- const pathFormat = requestFormatForPath(log.path);
3225
- return pathFormat === "unknown" ? log.apiFormat : pathFormat;
3049
+ function RequiredParameters({ names }) {
3050
+ if (names.length === 0) return null;
3051
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1", children: [
3052
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "required" }),
3053
+ names.slice(0, 4).map((name) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3054
+ Badge,
3055
+ {
3056
+ variant: "outline",
3057
+ className: "h-5 max-w-36 truncate rounded-md px-1.5 py-0 font-mono text-[10px]",
3058
+ children: name
3059
+ },
3060
+ name
3061
+ )),
3062
+ names.length > 4 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
3063
+ "+",
3064
+ names.length - 4
3065
+ ] })
3066
+ ] });
3067
+ }
3068
+ function ToolRow({ tool, called }) {
3069
+ const [open, setOpen] = reactExports.useState(false);
3070
+ const nameCopy = useCopyFeedback(tool.name);
3071
+ const hasDetails = tool.description !== null || tool.requiredParameters.length > 0 || tool.schema !== null;
3072
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border/60 bg-background/40", children: [
3073
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2 px-2.5 py-2", children: [
3074
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3075
+ "button",
3076
+ {
3077
+ type: "button",
3078
+ className: "flex min-w-0 flex-1 items-center gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3079
+ onClick: () => setOpen((value) => !value),
3080
+ "aria-expanded": open,
3081
+ disabled: !hasDetails,
3082
+ children: [
3083
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CategoryIcon, { category: tool.category }),
3084
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-xs font-semibold text-foreground/90", children: tool.name }),
3085
+ called && /* @__PURE__ */ jsxRuntimeExports.jsx(
3086
+ Badge,
3087
+ {
3088
+ variant: "outline",
3089
+ className: "h-5 rounded-md border-emerald-400/20 bg-emerald-400/5 px-1.5 py-0 text-[10px] text-emerald-300",
3090
+ children: "called"
3091
+ }
3092
+ ),
3093
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] text-muted-foreground", children: [
3094
+ tool.parameterCount,
3095
+ " params"
3096
+ ] }),
3097
+ tool.descriptionPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden min-w-0 truncate text-xs text-muted-foreground md:inline", children: tool.descriptionPreview }),
3098
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3099
+ hasDetails && (open ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5 shrink-0 text-muted-foreground" }))
3100
+ ]
3101
+ }
3102
+ ),
3103
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3104
+ "button",
3105
+ {
3106
+ type: "button",
3107
+ className: "inline-flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3108
+ onClick: nameCopy.copy,
3109
+ "aria-label": nameCopy.copied ? "Copied tool name" : "Copy tool name",
3110
+ children: nameCopy.copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
3111
+ }
3112
+ )
3113
+ ] }),
3114
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 border-t border-border/60 px-2.5 py-2", children: [
3115
+ tool.description !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: tool.description }),
3116
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RequiredParameters, { names: tool.requiredParameters }),
3117
+ tool.schema !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "max-h-64 rounded-md border border-border/60 bg-muted/20 p-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(JsonViewer, { data: safeJsonValue(tool.schema), defaultExpandDepth: 0 }) })
3118
+ ] }) })
3119
+ ] }) });
3120
+ }
3121
+ function matchesQuery(tool, query) {
3122
+ if (query.length === 0) return true;
3123
+ const haystack = `${tool.name} ${tool.description ?? ""} ${CATEGORY_LABELS[tool.category]}`.toLowerCase().trim();
3124
+ return haystack.includes(query);
3226
3125
  }
3126
+ const RequestTools = reactExports.memo(function RequestTools2({
3127
+ summary,
3128
+ calledToolNames
3129
+ }) {
3130
+ const [expanded, setExpanded] = reactExports.useState(false);
3131
+ const [query, setQuery] = reactExports.useState("");
3132
+ const calledNames = reactExports.useMemo(() => new Set(calledToolNames), [calledToolNames]);
3133
+ const normalizedQuery = query.trim().toLowerCase();
3134
+ const visibleTools = reactExports.useMemo(
3135
+ () => summary === null ? [] : summary.tools.filter((tool) => matchesQuery(tool, normalizedQuery)),
3136
+ [normalizedQuery, summary]
3137
+ );
3138
+ if (summary === null) return null;
3139
+ const previewTools = summary.tools.slice(0, 6);
3140
+ const hiddenPreviewCount = summary.tools.length - previewTools.length;
3141
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-3 rounded-lg border border-border/70 bg-muted/20", children: [
3142
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2 px-3 py-2", children: [
3143
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
3144
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-4 shrink-0 text-sky-300/80" }),
3145
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
3146
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: [
3147
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-semibold text-foreground", children: "Request Tools" }),
3148
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 rounded-md px-1.5 py-0 font-mono text-[10px]", children: summary.tools.length }),
3149
+ summary.toolChoiceLabel !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3150
+ Badge,
3151
+ {
3152
+ variant: "outline",
3153
+ className: "h-5 rounded-md px-1.5 py-0 font-mono text-[10px] text-muted-foreground",
3154
+ children: [
3155
+ "tool_choice: ",
3156
+ summary.toolChoiceLabel
3157
+ ]
3158
+ }
3159
+ )
3160
+ ] }),
3161
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex min-w-0 flex-wrap gap-1", children: summary.categories.map((category) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3162
+ Badge,
3163
+ {
3164
+ variant: "outline",
3165
+ className: cn(
3166
+ "h-5 rounded-md px-1.5 py-0 font-mono text-[10px]",
3167
+ CATEGORY_CLASSES[category.category]
3168
+ ),
3169
+ children: [
3170
+ category.count,
3171
+ " ",
3172
+ CATEGORY_LABELS[category.category]
3173
+ ]
3174
+ },
3175
+ category.category
3176
+ )) })
3177
+ ] })
3178
+ ] }),
3179
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3180
+ Button,
3181
+ {
3182
+ type: "button",
3183
+ variant: "ghost",
3184
+ size: "sm",
3185
+ className: "h-7 px-2",
3186
+ onClick: () => setExpanded((value) => !value),
3187
+ "aria-expanded": expanded,
3188
+ children: [
3189
+ expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5" }),
3190
+ expanded ? "Hide" : "Show"
3191
+ ]
3192
+ }
3193
+ )
3194
+ ] }),
3195
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap gap-1.5 border-t border-border/60 px-3 py-2", children: [
3196
+ previewTools.map((tool) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3197
+ Badge,
3198
+ {
3199
+ variant: "outline",
3200
+ className: cn(
3201
+ "h-5 max-w-44 truncate rounded-md px-1.5 py-0 font-mono text-[10px]",
3202
+ calledNames.has(tool.name) && "border-emerald-400/20 text-emerald-300"
3203
+ ),
3204
+ children: tool.name
3205
+ },
3206
+ tool.name
3207
+ )),
3208
+ hiddenPreviewCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-[10px] leading-5 text-muted-foreground", children: [
3209
+ "+",
3210
+ hiddenPreviewCount
3211
+ ] })
3212
+ ] }),
3213
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 border-t border-border/60 px-3 py-3", children: [
3214
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex h-8 items-center gap-2 rounded-md border border-border/70 bg-background/50 px-2 text-xs", children: [
3215
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "size-3.5 shrink-0 text-muted-foreground" }),
3216
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3217
+ "input",
3218
+ {
3219
+ value: query,
3220
+ onChange: (event) => setQuery(event.target.value),
3221
+ placeholder: "Filter tools",
3222
+ className: "min-w-0 flex-1 bg-transparent font-mono text-xs outline-none placeholder:text-muted-foreground/70"
3223
+ }
3224
+ )
3225
+ ] }),
3226
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-1.5", children: visibleTools.length > 0 ? visibleTools.map((tool) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolRow, { tool, called: calledNames.has(tool.name) }, tool.name)) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "py-2 text-xs text-muted-foreground", children: "No matching tools" }) })
3227
+ ] })
3228
+ ] });
3229
+ });
3227
3230
  const LOG_FOCUS_REQUEST_EVENT = "agent-inspector:focus-log";
3228
3231
  function dispatchLogFocusRequest(request) {
3229
3232
  window.dispatchEvent(new CustomEvent(LOG_FOCUS_REQUEST_EVENT, { detail: request }));
@@ -9948,6 +9951,13 @@ function truncateSessionId(id) {
9948
9951
  function shouldShowRawExport(captureMode) {
9949
9952
  return captureMode === "full";
9950
9953
  }
9954
+ function getExportActionVisibility(captureMode, logCount) {
9955
+ const hasLogs = logCount > 0;
9956
+ return {
9957
+ redacted: hasLogs,
9958
+ raw: hasLogs && shouldShowRawExport(captureMode)
9959
+ };
9960
+ }
9951
9961
  function computeTokenSummary(logs) {
9952
9962
  let totalIn = 0;
9953
9963
  let totalOut = 0;
@@ -10517,7 +10527,7 @@ function ProxyViewer({
10517
10527
  );
10518
10528
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
10519
10529
  const comparisonPredecessors = reactExports.useMemo(() => buildValidPredecessors(groups), [groups]);
10520
- const showRawExport = shouldShowRawExport(captureMode);
10530
+ const exportActionVisibility = getExportActionVisibility(captureMode, logs.length);
10521
10531
  const handleCompareWithPrevious = reactExports.useCallback(
10522
10532
  (log) => {
10523
10533
  const predecessor = comparisonPredecessors.get(log.id);
@@ -10641,7 +10651,7 @@ function ProxyViewer({
10641
10651
  logs.length !== 1 ? "s" : "",
10642
10652
  totalIn > 0 || totalOut > 0 ? ` - ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out` : ""
10643
10653
  ] }),
10644
- logs.length > 0 && showRawExport && /* @__PURE__ */ jsxRuntimeExports.jsx(
10654
+ exportActionVisibility.redacted && /* @__PURE__ */ jsxRuntimeExports.jsx(
10645
10655
  "button",
10646
10656
  {
10647
10657
  type: "button",
@@ -10657,7 +10667,7 @@ function ProxyViewer({
10657
10667
  ] })
10658
10668
  }
10659
10669
  ),
10660
- logs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
10670
+ exportActionVisibility.raw && /* @__PURE__ */ jsxRuntimeExports.jsx(
10661
10671
  "button",
10662
10672
  {
10663
10673
  type: "button",
@@ -10665,19 +10675,25 @@ function ProxyViewer({
10665
10675
  void handleExport("raw");
10666
10676
  },
10667
10677
  disabled: exporting !== null,
10668
- className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed rounded-md hover:bg-muted",
10678
+ className: "h-8 px-2.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1.5 rounded-md hover:bg-muted",
10669
10679
  title: "Export raw logs without redaction",
10670
- children: exporting === "raw" ? "Exporting..." : "Raw"
10680
+ children: exporting === "raw" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10681
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileBraces, { className: "size-3.5" }),
10682
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Raw" })
10683
+ ] })
10671
10684
  }
10672
10685
  ),
10673
- /* @__PURE__ */ jsxRuntimeExports.jsx(
10686
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
10674
10687
  "button",
10675
10688
  {
10676
10689
  type: "button",
10677
10690
  onClick: onClearAll,
10678
- className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer rounded-md hover:bg-muted",
10691
+ className: "h-8 px-3 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer inline-flex items-center gap-1.5 rounded-md hover:bg-muted",
10679
10692
  title: "Clear all logs",
10680
- children: "Clear"
10693
+ children: [
10694
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "size-3.5" }),
10695
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Clear" })
10696
+ ]
10681
10697
  }
10682
10698
  )
10683
10699
  ] }),