@tonyclaw/agent-inspector 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-BhRYPp6T.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-BHq0dkQG.js +114 -0
  4. package/.output/public/assets/ReplayDialog-Ch0gwg6B.js +1 -0
  5. package/.output/public/assets/{RequestAnatomy-BxX3_N9S.js → RequestAnatomy-CKbvIgaN.js} +1 -1
  6. package/.output/public/assets/ResponseView-hRLar32x.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-DCJ_gUHa.js +1 -0
  8. package/.output/public/assets/_sessionId-BJTz6t5l.js +1 -0
  9. package/.output/public/assets/index-Cf9U0Ekq.js +1 -0
  10. package/.output/public/assets/index-DTdv7nzl.css +1 -0
  11. package/.output/public/assets/{main-CKnTJ4-O.js → main-BlDtK0Nn.js} +6 -6
  12. package/.output/server/_libs/lucide-react.mjs +170 -108
  13. package/.output/server/_libs/radix-ui__react-collapsible.mjs +2 -2
  14. package/.output/server/{_sessionId-B-x9fRY3.mjs → _sessionId-Dy7rsLS4.mjs} +58 -5
  15. package/.output/server/_ssr/{CompareDrawer-BQVNsAY2.mjs → CompareDrawer-CkVX-ARV.mjs} +9 -8
  16. package/.output/server/_ssr/{ProxyViewerContainer-CYm2Dw19.mjs → ProxyViewerContainer-B5a9p3j5.mjs} +855 -31
  17. package/.output/server/_ssr/{ReplayDialog-CaMQBc79.mjs → ReplayDialog-hXY6H9fB.mjs} +7 -8
  18. package/.output/server/_ssr/{RequestAnatomy--P5arRH2.mjs → RequestAnatomy-B6MS7VvA.mjs} +59 -6
  19. package/.output/server/_ssr/{ResponseView-RtFwNvgD.mjs → ResponseView-ByjxwUnp.mjs} +5 -74
  20. package/.output/server/_ssr/{StreamingChunkSequence-B5HPkzab.mjs → StreamingChunkSequence-C5Kb8siT.mjs} +8 -7
  21. package/.output/server/_ssr/{index-CZIKZU43.mjs → index-BZXtj42m.mjs} +58 -5
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DGPt3MUc.mjs → router-CVQaVF_3.mjs} +5 -5
  24. package/.output/server/{_tanstack-start-manifest_v-BzH4pNaI.mjs → _tanstack-start-manifest_v-Bpe5ZC56.mjs} +1 -1
  25. package/.output/server/index.mjs +62 -69
  26. package/package.json +1 -1
  27. package/src/components/proxy-viewer/LogEntry.tsx +15 -1
  28. package/src/components/proxy-viewer/RequestToolsPanel.tsx +292 -0
  29. package/src/components/proxy-viewer/requestTools.ts +213 -0
  30. package/.output/public/assets/CompareDrawer-DDmqSAfl.js +0 -1
  31. package/.output/public/assets/ProxyViewerContainer-Cxpdziwd.js +0 -101
  32. package/.output/public/assets/ReplayDialog-Bt5DGzlh.js +0 -1
  33. package/.output/public/assets/ResponseView-Bl_5S9gZ.js +0 -1
  34. package/.output/public/assets/StreamingChunkSequence-RJMwNf6F.js +0 -1
  35. package/.output/public/assets/_sessionId-b4isaoDp.js +0 -1
  36. package/.output/public/assets/index-BZ4x5UI6.js +0 -1
  37. package/.output/public/assets/index-C624DUk9.css +0 -1
  38. package/.output/public/assets/json-viewer-CRL_gWEZ.js +0 -14
  39. package/.output/server/_ssr/json-viewer-d4obyRaA.mjs +0 -515
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@ import type { AnatomySegment } from "./anatomy/types";
22
22
  import { useAnatomyJump } from "./anatomy/useAnatomyJump";
23
23
  import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
24
24
  import { LogEntryHeader } from "./LogEntryHeader";
25
+ import { RequestTools } from "./RequestToolsPanel";
25
26
  import type { CacheTrendEntry } from "./cacheTrend";
26
27
  import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
27
28
  import {
@@ -29,6 +30,7 @@ import {
29
30
  shouldShowRawRequestTab,
30
31
  shouldShowRequestDiffButton,
31
32
  } from "./logEntryVisibility";
33
+ import { parseRequestTools } from "./requestTools";
32
34
 
33
35
  /**
34
36
  * Lightweight fallback for lazy-loaded tabs. Renders an empty 1px-tall row so
@@ -139,6 +141,10 @@ export const LogEntry = memo(function ({
139
141
  () => adapter.analyzeRequest(log.rawRequestBody),
140
142
  [adapter, log.rawRequestBody],
141
143
  );
144
+ const responseAnalysis = useMemo(
145
+ () => adapter.analyzeResponse(log.responseText),
146
+ [adapter, log.responseText],
147
+ );
142
148
  const strippedRequestBody = useMemo(() => {
143
149
  if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
144
150
  return null;
@@ -275,6 +281,10 @@ export const LogEntry = memo(function ({
275
281
  : null,
276
282
  [adapter, requestExpansion.parsedData],
277
283
  );
284
+ const requestTools = useMemo(
285
+ () => parseRequestTools(requestExpansion.parsedData),
286
+ [requestExpansion.parsedData],
287
+ );
278
288
  const anatomyPaths = useMemo(() => {
279
289
  if (anatomySegments === null) return undefined;
280
290
  return new Set(anatomySegments.map((s) => s.path));
@@ -316,11 +326,11 @@ export const LogEntry = memo(function ({
316
326
  <TabsList className="mx-4 mt-2">
317
327
  {viewMode === "full" && <TabsTrigger value="raw-headers">Raw Headers</TabsTrigger>}
318
328
  {viewMode === "full" && <TabsTrigger value="headers">Headers</TabsTrigger>}
329
+ {anatomySegments !== null && <TabsTrigger value="anatomy">Context</TabsTrigger>}
319
330
  {shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
320
331
  <TabsTrigger value="raw-request">Raw Request</TabsTrigger>
321
332
  )}
322
333
  <TabsTrigger value="request">Request</TabsTrigger>
323
- {anatomySegments !== null && <TabsTrigger value="anatomy">Context</TabsTrigger>}
324
334
  {viewMode === "full" && <TabsTrigger value="raw">Raw Response</TabsTrigger>}
325
335
  <TabsTrigger value="parsed">Response</TabsTrigger>
326
336
  </TabsList>
@@ -352,6 +362,10 @@ export const LogEntry = memo(function ({
352
362
  <TabsContent value="request">
353
363
  {activeTab === "request" && (
354
364
  <div className="px-4 pt-1 pb-3">
365
+ <RequestTools
366
+ summary={requestTools}
367
+ calledToolNames={responseAnalysis.toolNames ?? []}
368
+ />
355
369
  {/* All tab actions (Copy, Expand, Diff with Raw, Diff with
356
370
  Previous) live in the log header. The body is just
357
371
  the diff or JSON view, depending on toggle state. */}
@@ -0,0 +1,292 @@
1
+ import {
2
+ Blocks,
3
+ Check,
4
+ ChevronDown,
5
+ ChevronRight,
6
+ Code2,
7
+ Copy,
8
+ FileText,
9
+ Globe,
10
+ MoreHorizontal,
11
+ MousePointerClick,
12
+ Search,
13
+ Terminal,
14
+ Wrench,
15
+ } from "lucide-react";
16
+ import { memo, useMemo, useState, type JSX } from "react";
17
+ import { cn } from "../../lib/utils";
18
+ import { Badge } from "../ui/badge";
19
+ import { Button } from "../ui/button";
20
+ import { Collapsible, CollapsibleContent } from "../ui/collapsible";
21
+ import { JsonViewer } from "../ui/json-viewer";
22
+ import { safeJsonValue } from "../ui/json-viewer-bulk";
23
+ import { ScrollArea } from "../ui/scroll-area";
24
+ import type {
25
+ RequestToolCategory,
26
+ RequestToolDefinition,
27
+ RequestToolsSummary,
28
+ } from "./requestTools";
29
+ import { useCopyFeedback } from "./useCopyFeedback";
30
+
31
+ type RequestToolsProps = {
32
+ summary: RequestToolsSummary | null;
33
+ calledToolNames: readonly string[];
34
+ };
35
+
36
+ const CATEGORY_LABELS: Record<RequestToolCategory, string> = {
37
+ file: "file",
38
+ shell: "shell",
39
+ browser: "browser",
40
+ web: "web",
41
+ mcp: "MCP",
42
+ code: "code",
43
+ other: "other",
44
+ };
45
+
46
+ const CATEGORY_CLASSES: Record<RequestToolCategory, string> = {
47
+ file: "border-blue-400/20 bg-blue-400/5 text-blue-300",
48
+ shell: "border-teal-400/20 bg-teal-400/5 text-teal-300",
49
+ browser: "border-cyan-400/20 bg-cyan-400/5 text-cyan-300",
50
+ web: "border-sky-400/20 bg-sky-400/5 text-sky-300",
51
+ mcp: "border-violet-400/20 bg-violet-400/5 text-violet-300",
52
+ code: "border-emerald-400/20 bg-emerald-400/5 text-emerald-300",
53
+ other: "border-muted-foreground/20 bg-muted/30 text-muted-foreground",
54
+ };
55
+
56
+ function CategoryIcon({ category }: { category: RequestToolCategory }): JSX.Element {
57
+ switch (category) {
58
+ case "file":
59
+ return <FileText className="size-3.5" />;
60
+ case "shell":
61
+ return <Terminal className="size-3.5" />;
62
+ case "browser":
63
+ return <MousePointerClick className="size-3.5" />;
64
+ case "web":
65
+ return <Globe className="size-3.5" />;
66
+ case "mcp":
67
+ return <Blocks className="size-3.5" />;
68
+ case "code":
69
+ return <Code2 className="size-3.5" />;
70
+ case "other":
71
+ return <MoreHorizontal className="size-3.5" />;
72
+ }
73
+ }
74
+
75
+ function RequiredParameters({ names }: { names: readonly string[] }): JSX.Element | null {
76
+ if (names.length === 0) return null;
77
+ return (
78
+ <div className="flex min-w-0 flex-wrap items-center gap-1">
79
+ <span className="text-[10px] uppercase tracking-wide text-muted-foreground">required</span>
80
+ {names.slice(0, 4).map((name) => (
81
+ <Badge
82
+ key={name}
83
+ variant="outline"
84
+ className="h-5 max-w-36 truncate rounded-md px-1.5 py-0 font-mono text-[10px]"
85
+ >
86
+ {name}
87
+ </Badge>
88
+ ))}
89
+ {names.length > 4 && (
90
+ <span className="font-mono text-[10px] text-muted-foreground">+{names.length - 4}</span>
91
+ )}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolean }): JSX.Element {
97
+ const [open, setOpen] = useState(false);
98
+ const nameCopy = useCopyFeedback(tool.name);
99
+ const hasDetails =
100
+ tool.description !== null || tool.requiredParameters.length > 0 || tool.schema !== null;
101
+
102
+ return (
103
+ <Collapsible open={open} onOpenChange={setOpen}>
104
+ <div className="rounded-md border border-border/60 bg-background/40">
105
+ <div className="flex min-w-0 items-center gap-2 px-2.5 py-2">
106
+ <button
107
+ type="button"
108
+ 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"
109
+ onClick={() => setOpen((value) => !value)}
110
+ aria-expanded={open}
111
+ disabled={!hasDetails}
112
+ >
113
+ <CategoryIcon category={tool.category} />
114
+ <span className="min-w-0 truncate font-mono text-xs font-semibold text-foreground/90">
115
+ {tool.name}
116
+ </span>
117
+ {called && (
118
+ <Badge
119
+ variant="outline"
120
+ className="h-5 rounded-md border-emerald-400/20 bg-emerald-400/5 px-1.5 py-0 text-[10px] text-emerald-300"
121
+ >
122
+ called
123
+ </Badge>
124
+ )}
125
+ <span className="font-mono text-[10px] text-muted-foreground">
126
+ {tool.parameterCount} params
127
+ </span>
128
+ {tool.descriptionPreview !== null && (
129
+ <span className="hidden min-w-0 truncate text-xs text-muted-foreground md:inline">
130
+ {tool.descriptionPreview}
131
+ </span>
132
+ )}
133
+ <span className="flex-1" />
134
+ {hasDetails &&
135
+ (open ? (
136
+ <ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
137
+ ) : (
138
+ <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
139
+ ))}
140
+ </button>
141
+ <button
142
+ type="button"
143
+ 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"
144
+ onClick={nameCopy.copy}
145
+ aria-label={nameCopy.copied ? "Copied tool name" : "Copy tool name"}
146
+ >
147
+ {nameCopy.copied ? (
148
+ <Check className="size-3.5 text-emerald-400" />
149
+ ) : (
150
+ <Copy className="size-3.5" />
151
+ )}
152
+ </button>
153
+ </div>
154
+ <CollapsibleContent>
155
+ <div className="space-y-2 border-t border-border/60 px-2.5 py-2">
156
+ {tool.description !== null && (
157
+ <p className="text-xs leading-relaxed text-muted-foreground">{tool.description}</p>
158
+ )}
159
+ <RequiredParameters names={tool.requiredParameters} />
160
+ {tool.schema !== null && (
161
+ <ScrollArea className="max-h-64 rounded-md border border-border/60 bg-muted/20 p-2">
162
+ <JsonViewer data={safeJsonValue(tool.schema)} defaultExpandDepth={0} />
163
+ </ScrollArea>
164
+ )}
165
+ </div>
166
+ </CollapsibleContent>
167
+ </div>
168
+ </Collapsible>
169
+ );
170
+ }
171
+
172
+ function matchesQuery(tool: RequestToolDefinition, query: string): boolean {
173
+ if (query.length === 0) return true;
174
+ const haystack = `${tool.name} ${tool.description ?? ""} ${CATEGORY_LABELS[tool.category]}`
175
+ .toLowerCase()
176
+ .trim();
177
+ return haystack.includes(query);
178
+ }
179
+
180
+ export const RequestTools = memo(function RequestTools({
181
+ summary,
182
+ calledToolNames,
183
+ }: RequestToolsProps): JSX.Element | null {
184
+ const [expanded, setExpanded] = useState(false);
185
+ const [query, setQuery] = useState("");
186
+ const calledNames = useMemo(() => new Set(calledToolNames), [calledToolNames]);
187
+ const normalizedQuery = query.trim().toLowerCase();
188
+ const visibleTools = useMemo(
189
+ () =>
190
+ summary === null ? [] : summary.tools.filter((tool) => matchesQuery(tool, normalizedQuery)),
191
+ [normalizedQuery, summary],
192
+ );
193
+
194
+ if (summary === null) return null;
195
+
196
+ const previewTools = summary.tools.slice(0, 6);
197
+ const hiddenPreviewCount = summary.tools.length - previewTools.length;
198
+
199
+ return (
200
+ <section className="mb-3 rounded-lg border border-border/70 bg-muted/20">
201
+ <div className="flex min-w-0 flex-wrap items-center gap-2 px-3 py-2">
202
+ <div className="flex min-w-0 flex-1 items-center gap-2">
203
+ <Wrench className="size-4 shrink-0 text-sky-300/80" />
204
+ <div className="min-w-0">
205
+ <div className="flex min-w-0 flex-wrap items-center gap-1.5">
206
+ <span className="text-sm font-semibold text-foreground">Request Tools</span>
207
+ <Badge variant="outline" className="h-5 rounded-md px-1.5 py-0 font-mono text-[10px]">
208
+ {summary.tools.length}
209
+ </Badge>
210
+ {summary.toolChoiceLabel !== null && (
211
+ <Badge
212
+ variant="outline"
213
+ className="h-5 rounded-md px-1.5 py-0 font-mono text-[10px] text-muted-foreground"
214
+ >
215
+ tool_choice: {summary.toolChoiceLabel}
216
+ </Badge>
217
+ )}
218
+ </div>
219
+ <div className="mt-1 flex min-w-0 flex-wrap gap-1">
220
+ {summary.categories.map((category) => (
221
+ <Badge
222
+ key={category.category}
223
+ variant="outline"
224
+ className={cn(
225
+ "h-5 rounded-md px-1.5 py-0 font-mono text-[10px]",
226
+ CATEGORY_CLASSES[category.category],
227
+ )}
228
+ >
229
+ {category.count} {CATEGORY_LABELS[category.category]}
230
+ </Badge>
231
+ ))}
232
+ </div>
233
+ </div>
234
+ </div>
235
+ <Button
236
+ type="button"
237
+ variant="ghost"
238
+ size="sm"
239
+ className="h-7 px-2"
240
+ onClick={() => setExpanded((value) => !value)}
241
+ aria-expanded={expanded}
242
+ >
243
+ {expanded ? <ChevronDown className="size-3.5" /> : <ChevronRight className="size-3.5" />}
244
+ {expanded ? "Hide" : "Show"}
245
+ </Button>
246
+ </div>
247
+
248
+ <div className="flex min-w-0 flex-wrap gap-1.5 border-t border-border/60 px-3 py-2">
249
+ {previewTools.map((tool) => (
250
+ <Badge
251
+ key={tool.name}
252
+ variant="outline"
253
+ className={cn(
254
+ "h-5 max-w-44 truncate rounded-md px-1.5 py-0 font-mono text-[10px]",
255
+ calledNames.has(tool.name) && "border-emerald-400/20 text-emerald-300",
256
+ )}
257
+ >
258
+ {tool.name}
259
+ </Badge>
260
+ ))}
261
+ {hiddenPreviewCount > 0 && (
262
+ <span className="font-mono text-[10px] leading-5 text-muted-foreground">
263
+ +{hiddenPreviewCount}
264
+ </span>
265
+ )}
266
+ </div>
267
+
268
+ {expanded && (
269
+ <div className="space-y-2 border-t border-border/60 px-3 py-3">
270
+ <label className="flex h-8 items-center gap-2 rounded-md border border-border/70 bg-background/50 px-2 text-xs">
271
+ <Search className="size-3.5 shrink-0 text-muted-foreground" />
272
+ <input
273
+ value={query}
274
+ onChange={(event) => setQuery(event.target.value)}
275
+ placeholder="Filter tools"
276
+ className="min-w-0 flex-1 bg-transparent font-mono text-xs outline-none placeholder:text-muted-foreground/70"
277
+ />
278
+ </label>
279
+ <div className="grid gap-1.5">
280
+ {visibleTools.length > 0 ? (
281
+ visibleTools.map((tool) => (
282
+ <ToolRow key={tool.name} tool={tool} called={calledNames.has(tool.name)} />
283
+ ))
284
+ ) : (
285
+ <p className="py-2 text-xs text-muted-foreground">No matching tools</p>
286
+ )}
287
+ </div>
288
+ </div>
289
+ )}
290
+ </section>
291
+ );
292
+ });
@@ -0,0 +1,213 @@
1
+ import { safeGetOwnProperty } from "../../lib/objectUtils";
2
+
3
+ export type RequestToolCategory = "file" | "shell" | "browser" | "web" | "mcp" | "code" | "other";
4
+
5
+ export type RequestToolDefinition = {
6
+ name: string;
7
+ description: string | null;
8
+ descriptionPreview: string | null;
9
+ category: RequestToolCategory;
10
+ parameterCount: number;
11
+ requiredParameters: string[];
12
+ schema: unknown | null;
13
+ };
14
+
15
+ export type RequestToolCategorySummary = {
16
+ category: RequestToolCategory;
17
+ count: number;
18
+ };
19
+
20
+ export type RequestToolsSummary = {
21
+ tools: RequestToolDefinition[];
22
+ toolChoiceLabel: string | null;
23
+ categories: RequestToolCategorySummary[];
24
+ };
25
+
26
+ const CATEGORY_ORDER: RequestToolCategory[] = [
27
+ "file",
28
+ "shell",
29
+ "browser",
30
+ "web",
31
+ "mcp",
32
+ "code",
33
+ "other",
34
+ ];
35
+
36
+ function firstTextSlice(value: string): string {
37
+ const normalized = value.replace(/\s+/g, " ").trim();
38
+ if (normalized.length <= 120) return normalized;
39
+ return `${normalized.slice(0, 119)}...`;
40
+ }
41
+
42
+ function previewDescription(description: string | null): string | null {
43
+ if (description === null) return null;
44
+ const normalized = description.replace(/\s+/g, " ").trim();
45
+ if (normalized.length === 0) return null;
46
+ const sentenceEnd = normalized.search(/[.!?。!?]/);
47
+ if (sentenceEnd >= 24) return firstTextSlice(normalized.slice(0, sentenceEnd + 1));
48
+ return firstTextSlice(normalized);
49
+ }
50
+
51
+ function lowerHaystack(name: string, description: string | null): string {
52
+ return `${name} ${description ?? ""}`.toLowerCase();
53
+ }
54
+
55
+ function includesAny(value: string, needles: readonly string[]): boolean {
56
+ return needles.some((needle) => value.includes(needle));
57
+ }
58
+
59
+ function categorizeTool(name: string, description: string | null): RequestToolCategory {
60
+ const haystack = lowerHaystack(name, description);
61
+ if (
62
+ name.startsWith("mcp__") ||
63
+ name.startsWith("inspector_") ||
64
+ includesAny(haystack, [" mcp", "model context protocol"])
65
+ ) {
66
+ return "mcp";
67
+ }
68
+ if (
69
+ includesAny(haystack, ["bash", "shell", "terminal", "command", "powershell", "exec", "process"])
70
+ ) {
71
+ return "shell";
72
+ }
73
+ if (
74
+ includesAny(haystack, [
75
+ "browser",
76
+ "playwright",
77
+ "screenshot",
78
+ "click",
79
+ "navigate",
80
+ "page",
81
+ "tab",
82
+ ])
83
+ ) {
84
+ return "browser";
85
+ }
86
+ if (
87
+ includesAny(haystack, [
88
+ "read",
89
+ "write",
90
+ "edit",
91
+ "file",
92
+ "glob",
93
+ "grep",
94
+ "patch",
95
+ "filesystem",
96
+ "directory",
97
+ ])
98
+ ) {
99
+ return "file";
100
+ }
101
+ if (includesAny(haystack, ["web", "search", "http", "url", "fetch"])) {
102
+ return "web";
103
+ }
104
+ if (includesAny(haystack, ["code", "typescript", "javascript", "python", "repl"])) {
105
+ return "code";
106
+ }
107
+ return "other";
108
+ }
109
+
110
+ function parameterNames(schema: unknown): string[] {
111
+ const properties = safeGetOwnProperty(schema, "properties");
112
+ if (properties === null || typeof properties !== "object" || Array.isArray(properties)) {
113
+ return [];
114
+ }
115
+ return Object.keys(properties).sort((a, b) => a.localeCompare(b));
116
+ }
117
+
118
+ function requiredParameterNames(schema: unknown): string[] {
119
+ const required = safeGetOwnProperty(schema, "required");
120
+ if (!Array.isArray(required)) return [];
121
+ const names: string[] = [];
122
+ for (const item of required) {
123
+ if (typeof item === "string" && item.length > 0) names.push(item);
124
+ }
125
+ return names.sort((a, b) => a.localeCompare(b));
126
+ }
127
+
128
+ function parseAnthropicTool(tool: unknown): RequestToolDefinition | null {
129
+ const name = safeGetOwnProperty(tool, "name");
130
+ if (typeof name !== "string" || name.length === 0) return null;
131
+ const description = safeGetOwnProperty(tool, "description");
132
+ const schema = safeGetOwnProperty(tool, "input_schema") ?? null;
133
+ const normalizedDescription = typeof description === "string" ? description : null;
134
+ return {
135
+ name,
136
+ description: normalizedDescription,
137
+ descriptionPreview: previewDescription(normalizedDescription),
138
+ category: categorizeTool(name, normalizedDescription),
139
+ parameterCount: parameterNames(schema).length,
140
+ requiredParameters: requiredParameterNames(schema),
141
+ schema,
142
+ };
143
+ }
144
+
145
+ function parseOpenAITool(tool: unknown): RequestToolDefinition | null {
146
+ const fn = safeGetOwnProperty(tool, "function");
147
+ const name = safeGetOwnProperty(fn, "name");
148
+ if (typeof name !== "string" || name.length === 0) return null;
149
+ const description = safeGetOwnProperty(fn, "description");
150
+ const schema = safeGetOwnProperty(fn, "parameters") ?? null;
151
+ const normalizedDescription = typeof description === "string" ? description : null;
152
+ return {
153
+ name,
154
+ description: normalizedDescription,
155
+ descriptionPreview: previewDescription(normalizedDescription),
156
+ category: categorizeTool(name, normalizedDescription),
157
+ parameterCount: parameterNames(schema).length,
158
+ requiredParameters: requiredParameterNames(schema),
159
+ schema,
160
+ };
161
+ }
162
+
163
+ function parseTool(tool: unknown): RequestToolDefinition | null {
164
+ const openAITool = parseOpenAITool(tool);
165
+ if (openAITool !== null) return openAITool;
166
+ return parseAnthropicTool(tool);
167
+ }
168
+
169
+ function formatToolChoice(choice: unknown): string | null {
170
+ if (choice === undefined || choice === null) return null;
171
+ if (typeof choice === "string") return choice;
172
+ const type = safeGetOwnProperty(choice, "type");
173
+ const name = safeGetOwnProperty(choice, "name");
174
+ const fnName = safeGetOwnProperty(safeGetOwnProperty(choice, "function"), "name");
175
+ if (typeof type === "string" && typeof fnName === "string" && fnName.length > 0) {
176
+ return `${type}: ${fnName}`;
177
+ }
178
+ if (typeof type === "string" && typeof name === "string" && name.length > 0) {
179
+ return `${type}: ${name}`;
180
+ }
181
+ if (typeof type === "string") return type;
182
+ const encoded = JSON.stringify(choice);
183
+ return encoded === undefined ? null : firstTextSlice(encoded);
184
+ }
185
+
186
+ function summarizeCategories(tools: RequestToolDefinition[]): RequestToolCategorySummary[] {
187
+ const counts = new Map<RequestToolCategory, number>();
188
+ for (const tool of tools) {
189
+ counts.set(tool.category, (counts.get(tool.category) ?? 0) + 1);
190
+ }
191
+ return CATEGORY_ORDER.flatMap((category) => {
192
+ const count = counts.get(category) ?? 0;
193
+ return count > 0 ? [{ category, count }] : [];
194
+ });
195
+ }
196
+
197
+ export function parseRequestTools(parsed: unknown): RequestToolsSummary | null {
198
+ const rawTools = safeGetOwnProperty(parsed, "tools");
199
+ if (!Array.isArray(rawTools) || rawTools.length === 0) return null;
200
+
201
+ const tools: RequestToolDefinition[] = [];
202
+ for (const rawTool of rawTools) {
203
+ const tool = parseTool(rawTool);
204
+ if (tool !== null) tools.push(tool);
205
+ }
206
+ if (tools.length === 0) return null;
207
+
208
+ return {
209
+ tools,
210
+ toolChoiceLabel: formatToolChoice(safeGetOwnProperty(parsed, "tool_choice")),
211
+ categories: summarizeCategories(tools),
212
+ };
213
+ }
@@ -1 +0,0 @@
1
- import{r as h,j as t}from"./main-CKnTJ4-O.js";import{c as X,g as O,r as P,a as q,X as Y,b as x,B as Z,f as $,R as ee,C as te,M as _,d as J,e as M,h as V,i as re,j as ne}from"./ProxyViewerContainer-Cxpdziwd.js";import{J as N}from"./json-viewer-CRL_gWEZ.js";const se=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],ae=X("equal",se),oe="";function j(e){if(e.length===0)return oe;let r="";for(let n=0;n<e.length;n++){const s=e[n];s!==void 0&&(typeof s=="number"?r+=`[${s}]`:n===0?r+=s:r+=`.${s}`)}return r}function de(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e){if(typeof e=="string")try{return C(JSON.parse(e))}catch{return{kind:"primitive",value:e}}return C(e)}function C(e){if(e===null)return{kind:"primitive",value:null};if(typeof e=="string")return{kind:"primitive",value:e};if(typeof e=="number")return{kind:"primitive",value:e};if(typeof e=="boolean")return{kind:"primitive",value:e};if(Array.isArray(e))return{kind:"array",value:e.map(r=>C(r))};if(de(e)){const r={};for(const n of Object.keys(e).sort())r[n]=C(e[n]);return{kind:"object",value:r}}return{kind:"primitive",value:null}}function ie(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(E(r,n)){s.push({kind:"equal",path:d,value:r});return}if(r.kind!==n.kind){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="primitive"&&n.kind==="primitive"){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="object"&&n.kind==="object"){const o=Object.keys(r.value),a=Object.keys(n.value),m=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!m.has(i))s.push({kind:"removed",path:j([...e,i]),value:f});else{const p=n.value[i];if(p===void 0)continue;R([...e,i],f,p,s)}}for(const i of a){if(o.includes(i))continue;const f=n.value[i];f!==void 0&&s.push({kind:"added",path:j([...e,i]),value:f})}return}if(r.kind==="array"&&n.kind==="array"){const o=Math.min(r.value.length,n.value.length);for(let a=0;a<o;a++){const m=r.value[a],i=n.value[a];m===void 0||i===void 0||R([...e,a],m,i,s)}for(let a=o;a<n.value.length;a++){const m=n.value[a];m!==void 0&&s.push({kind:"added",path:j([...e,a]),value:m})}for(let a=o;a<r.value.length;a++){const m=r.value[a];m!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:m})}}}function E(e,r){if(e.kind!==r.kind)return!1;if(e.kind==="primitive"&&r.kind==="primitive")return e.value===r.value;if(e.kind==="array"&&r.kind==="array"){if(e.value.length!==r.value.length)return!1;for(let n=0;n<e.value.length;n++){const s=e.value[n],d=r.value[n];if(s===void 0||d===void 0||!E(s,d))return!1}return!0}if(e.kind==="object"&&r.kind==="object"){const n=Object.keys(e.value),s=Object.keys(r.value);if(n.length!==s.length)return!1;for(const d of n){const o=e.value[d],a=r.value[d];if(o===void 0||a===void 0||!E(o,a))return!1}return!0}return!1}function v(e,r=80){let n;switch(e.kind){case"primitive":n=e.value===null?"null":JSON.stringify(e.value);break;case"array":n=`[… ${e.value.length} items]`;break;case"object":n=`{… ${Object.keys(e.value).length} keys}`;break}return n.length>r&&(n=`${n.slice(0,r-1)}…`),n}function w(e,r=2){return JSON.stringify(S(e),null,r)}function S(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(S);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=S(s);return r}}}function K(e){if(e==="")return"";for(let r=e.length-1;r>=0;r--){const n=e[r];if(n==="."||n==="[")return e.substring(0,r)}return""}function L(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function le(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&L(s)){const d=K(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!L(a)||K(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let m=n;m<o;m++){const i=e[m];i!==void 0&&i.kind==="equal"&&a.push(i)}r.push({kind:"equal-run",ops:a}),n=o;continue}}s!==void 0&&r.push({kind:"single",op:s}),n++}return r}const B={added:{icon:J,accent:"text-emerald-600 dark:text-emerald-400",bg:"bg-emerald-500/5 hover:bg-emerald-500/10",border:"border-l-emerald-500",label:"ADDED"},removed:{icon:_,accent:"text-rose-600 dark:text-rose-400",bg:"bg-rose-500/5 hover:bg-rose-500/10",border:"border-l-rose-500",label:"REMOVED"},changed:{icon:M,accent:"text-amber-600 dark:text-amber-400",bg:"bg-amber-500/5 hover:bg-amber-500/10",border:"border-l-amber-500",label:"CHANGED"},equal:{icon:ae,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ce({ops:e,expanded:r,onToggle:n}){const s=e[0],d=e[e.length-1];if(s===void 0||d===void 0)return t.jsx("div",{className:"text-muted-foreground/40 text-xs",children:"—"});const o=s.path,a=d.path,m=e.length===1?o:`${o} … ${a}`,i=s.value.kind==="array"?`${e.length} equal arrays`:s.value.kind==="object"?`${e.length} equal objects`:"equal",f=B.equal;return t.jsxs("div",{className:x("border-l-4 rounded-sm",f.border,f.bg),children:[t.jsxs("button",{type:"button",onClick:n,className:"w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",children:[t.jsx(V,{className:x("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:x("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:m}),t.jsx("span",{className:x("text-[10px] uppercase tracking-wider shrink-0",f.accent),children:f.label}),t.jsxs("span",{className:"text-muted-foreground/60 shrink-0",children:["(",i,")"]})]}),r&&t.jsx("div",{className:"ml-5 mt-1 mb-2 space-y-2 pr-2",children:e.map(p=>t.jsxs("div",{className:"border border-border/50 rounded p-2 bg-muted/20",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:p.path}),t.jsx(N,{text:w(p.value),defaultExpandDepth:0})]},p.path))})]})}function ue({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=B[e.kind],m=a.icon,i=e.kind==="added"||e.kind==="removed"?e.value.kind==="object"||e.value.kind==="array":e.kind==="changed"?e.left.kind==="object"||e.left.kind==="array"||e.right.kind==="object"||e.right.kind==="array":!1,f=e.kind==="changed"?[{text:v(e.left,400),tone:"text-rose-700 dark:text-rose-300 line-through"},{text:v(e.right,400),tone:"text-emerald-700 dark:text-emerald-300"}]:e.kind==="removed"?[{text:v(e.value,400),tone:"text-rose-700 dark:text-rose-300 line-through"}]:e.kind==="added"?[{text:v(e.value,400),tone:"text-emerald-700 dark:text-emerald-300"}]:[{text:v(e.value,400),tone:"text-muted-foreground"}],p=n===e.path&&e.path!=="";return t.jsxs("div",{"data-diff-idx":r,"data-diff-kind":e.kind,className:x("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors",a.border,a.bg),children:[t.jsxs("button",{type:"button",onClick:o,disabled:!i,className:x("w-full flex items-center gap-2 text-xs text-left rounded-sm",i?"cursor-pointer":"cursor-default"),"aria-expanded":i?d:void 0,"aria-label":i?d?`Collapse ${e.path||"root"}`:`Expand ${e.path||"root"}`:void 0,children:[i?t.jsx(V,{className:x("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(m,{className:x("size-3.5 shrink-0",a.accent),strokeWidth:2.5}),t.jsx("span",{className:"font-mono truncate flex-1 min-w-0",title:e.path||"(root)",children:e.path===""?"(root)":e.path}),t.jsx("span",{className:x("text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",a.accent,e.kind==="equal"?"bg-muted/40":"bg-background/60"),children:a.label}),e.path!==""&&t.jsx("span",{role:"button",tabIndex:0,onClick:b=>{b.stopPropagation(),s(e.path)},onKeyDown:b=>{(b.key==="Enter"||b.key===" ")&&(b.stopPropagation(),b.preventDefault(),s(e.path))},className:x("shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",p?"text-emerald-500":"text-muted-foreground/50 hover:text-foreground hover:bg-muted"),"aria-label":p?"Copied":"Copy",title:p?"Copied!":"Copy",children:p?t.jsx(re,{className:"size-3"}):t.jsx(ne,{className:"size-3"})})]}),f.map((b,y)=>t.jsx("div",{className:x("font-mono text-xs mt-1 break-all pl-5",b.tone),children:b.text},y)),t.jsx("div",{className:"overflow-hidden transition-all duration-200",style:{maxHeight:d&&i?"2000px":"0"},"aria-hidden":!d,children:d&&i&&e.kind!=="equal"?t.jsx(me,{op:e}):null})]})}function me({op:e}){if(e.kind==="added"||e.kind==="removed")return t.jsx("div",{className:"pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20",children:t.jsx(N,{text:w(e.value),defaultExpandDepth:0})});const r=e.left.kind==="object"||e.left.kind==="array",n=e.right.kind==="object"||e.right.kind==="array";return!r&&!n?t.jsx("div",{className:"pl-5 mt-2 text-xs text-muted-foreground/70 italic",children:"Primitive values are shown inline above."}):t.jsxs("div",{className:"pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2",children:[t.jsxs("div",{className:"border border-rose-500/30 rounded p-2 bg-rose-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-rose-500 mb-1",children:"Old"}),t.jsx(N,{text:w(e.left),defaultExpandDepth:0})]}),t.jsxs("div",{className:"border border-emerald-500/30 rounded p-2 bg-emerald-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-emerald-500 mb-1",children:"New"}),t.jsx(N,{text:w(e.right),defaultExpandDepth:0})]})]})}function xe({counts:e,onJumpTo:r}){const n=e.added+e.removed+e.changed;return t.jsxs("div",{className:"px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap",children:[t.jsxs("span",{className:"text-muted-foreground font-medium",children:[n," ",n===1?"change":"changes"]}),t.jsxs("button",{type:"button",onClick:()=>r("removed"),disabled:e.removed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.removed>0?"border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.removed>0?"Jump to first removed":"No removals",children:[t.jsx(_,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.added>0?"border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.added>0?"Jump to first added":"No additions",children:[t.jsx(J,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.changed>0?"border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.changed>0?"Jump to first changed":"No changes",children:[t.jsx(M,{className:"size-3"}),e.changed," changed"]})]})}function fe({mode:e,onChange:r}){return t.jsxs("div",{className:"inline-flex rounded-md border border-border overflow-hidden",children:[t.jsxs("button",{type:"button",onClick:()=>r("unified"),"aria-pressed":e==="unified",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",e==="unified"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Unified view (single column, emphasized diffs)",children:[t.jsx(ee,{className:"size-3"}),"Unified"]}),t.jsxs("button",{type:"button",onClick:()=>r("split"),"aria-pressed":e==="split",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",e==="split"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Split view (path | left | right)",children:[t.jsx(te,{className:"size-3"}),"Split"]})]})}function A({log:e,side:r}){const n=q(e);return t.jsxs("div",{className:"flex-1 min-w-0 space-y-1 text-xs",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Z,{variant:"outline",className:x("text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",r==="left"?"border-rose-500/40 text-rose-400":"border-emerald-500/40 text-emerald-400"),children:r==="left"?"← Left":"Right →"}),t.jsxs("span",{className:"font-mono text-blue-400/80",children:["#",e.id]}),e.model!==null&&t.jsx("span",{className:"font-mono text-muted-foreground truncate",children:e.model})]}),t.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground font-mono",children:[e.cacheCreationInputTokens!==null&&e.cacheCreationInputTokens>0&&t.jsxs("span",{className:"text-emerald-400",children:["KV Cache +",$(e.cacheCreationInputTokens)]}),e.cacheReadInputTokens!==null&&e.cacheReadInputTokens>0&&t.jsxs("span",{className:"text-purple-400",children:["KV Cache ~",$(e.cacheReadInputTokens)]}),t.jsx("span",{className:"truncate",title:e.timestamp,children:e.timestamp})]}),t.jsxs("div",{className:"text-muted-foreground/70 font-mono truncate",title:n,children:["session: ",n]})]})}function ge({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=O(P(e)).analyzeRequest(e.rawRequestBody),c=O(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return ie(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>le(s),[s]),o=h.useMemo(()=>{let l=0,c=0,u=0;for(const g of d)if(g.kind==="single")switch(g.op.kind){case"added":l++;break;case"removed":c++;break;case"changed":u++;break}return{added:l,removed:c,changed:u}},[d]),[a,m]=h.useState(new Set),i=l=>{m(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})},[f,p]=h.useState(new Set),b=l=>{p(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})};h.useEffect(()=>{p(new Set)},[e.id,r.id]);const[y,F]=h.useState("unified"),T=h.useRef(null),[U,z]=h.useState(null),k=h.useRef(null),H=l=>{window.navigator.clipboard.writeText(l).then(()=>{z(l),k.current!==null&&clearTimeout(k.current),k.current=setTimeout(()=>z(null),1500)})};h.useEffect(()=>()=>{k.current!==null&&clearTimeout(k.current)},[]);const G=l=>{const c=d.findIndex(I=>I.kind==="single"&&I.op.kind===l);if(c===-1)return;const u=T.current;if(u===null)return;const g=u.querySelector(`[data-diff-idx="${c}"]`);g!==null&&g.scrollIntoView({behavior:"smooth",block:"center"})};h.useEffect(()=>{const l=u=>{u.key==="Escape"&&n()};document.addEventListener("keydown",l);const c=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.removeEventListener("keydown",l),document.body.style.overflow=c}},[n]);const Q=q(e)===q(r),W=s.length===1&&s[0]?.kind==="equal";return t.jsxs("div",{className:"fixed inset-0 z-50 flex justify-end",role:"dialog","aria-modal":"true","aria-label":"Compare two log requests",children:[t.jsx("button",{type:"button",onClick:n,"aria-label":"Close compare drawer",className:"absolute inset-0 bg-black/40 cursor-default",tabIndex:-1}),t.jsxs("div",{className:x("relative bg-background border-l border-border shadow-xl","w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"),onClick:l=>l.stopPropagation(),onKeyDown:l=>l.stopPropagation(),children:[t.jsxs("div",{className:"flex items-start gap-4 px-4 py-3 border-b border-border",children:[t.jsxs("div",{className:"flex-1 flex gap-4 min-w-0",children:[t.jsx(A,{log:e,side:"left"}),t.jsx(A,{log:r,side:"right"})]}),t.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[t.jsx(fe,{mode:y,onChange:F}),t.jsx("button",{type:"button",onClick:n,"aria-label":"Close",className:"p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",children:t.jsx(Y,{className:"size-4"})})]})]}),!Q&&t.jsx("div",{className:"px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border",children:"Heads up: the two selected logs are from different sessions."}),W?t.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm",children:"The two Request payloads are identical."}):t.jsxs(t.Fragment,{children:[t.jsx(xe,{counts:o,onJumpTo:G}),t.jsx("div",{ref:T,className:"flex-1 min-h-0 overflow-y-auto",children:y==="unified"?t.jsx("div",{className:"px-3 py-2 space-y-0.5",children:d.map((l,c)=>{if(l.kind==="equal-run")return t.jsx(ce,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(ue,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(pe,{grouped:d,left:e,right:r})})]})]})]})}function pe({grouped:e,left:r,right:n}){return t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs",children:[t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground",children:[t.jsx("span",{children:"Path"}),t.jsxs("span",{children:["Left (Log #",r.id,")"]}),t.jsxs("span",{children:["Right (Log #",n.id,")"]})]}),e.map((s,d)=>{if(s.kind==="equal-run")return t.jsxs("div",{className:"col-span-3 px-2 py-1 text-xs text-muted-foreground/60",children:[s.ops.length," equal siblings collapsed — switch to Unified to expand"]},d);const o=s.op;return o.kind==="equal"?t.jsxs("div",{className:"col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",children:[t.jsx("span",{className:"font-mono text-xs truncate",title:o.path,children:o.path}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)})]},d):o.kind==="added"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-emerald-300/90",children:["+ ",v(o.value,400)]})]},d):o.kind==="removed"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-rose-300/90 line-through",children:["− ",v(o.value,400)]})]},d):t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:o.path}),t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[t.jsx("div",{className:"font-mono text-rose-300/90 break-all line-through",children:v(o.left,400)}),t.jsx("div",{className:"font-mono text-emerald-300/90 break-all",children:v(o.right,400)})]})]},d)})]})}export{ge as CompareDrawer};