@tonyclaw/agent-inspector 2.0.5 → 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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-BhRYPp6T.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-BHq0dkQG.js +114 -0
- package/.output/public/assets/ReplayDialog-Ch0gwg6B.js +1 -0
- package/.output/public/assets/RequestAnatomy-CKbvIgaN.js +1 -0
- package/.output/public/assets/ResponseView-hRLar32x.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-DCJ_gUHa.js +1 -0
- package/.output/public/assets/_sessionId-BJTz6t5l.js +1 -0
- package/.output/public/assets/index-Cf9U0Ekq.js +1 -0
- package/.output/public/assets/index-DTdv7nzl.css +1 -0
- package/.output/public/assets/{main-2NlGzgOe.js → main-BlDtK0Nn.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +170 -108
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +2 -2
- package/.output/server/{_sessionId-DWCTasJU.mjs → _sessionId-Dy7rsLS4.mjs} +58 -5
- package/.output/server/_ssr/{CompareDrawer-DhrN1uC2.mjs → CompareDrawer-CkVX-ARV.mjs} +9 -8
- package/.output/server/_ssr/{ProxyViewerContainer-DRl51s_n.mjs → ProxyViewerContainer-B5a9p3j5.mjs} +856 -32
- package/.output/server/_ssr/{ReplayDialog-BQT_ygxC.mjs → ReplayDialog-hXY6H9fB.mjs} +7 -8
- package/.output/server/_ssr/RequestAnatomy-B6MS7VvA.mjs +576 -0
- package/.output/server/_ssr/{ResponseView-e0kL2C3x.mjs → ResponseView-ByjxwUnp.mjs} +5 -74
- package/.output/server/_ssr/{StreamingChunkSequence-BJG-m7xs.mjs → StreamingChunkSequence-C5Kb8siT.mjs} +8 -7
- package/.output/server/_ssr/{index-Dea3OeRw.mjs → index-BZXtj42m.mjs} +58 -5
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-Dl7oh0zx.mjs → router-CVQaVF_3.mjs} +5 -5
- package/.output/server/{_tanstack-start-manifest_v-m-FJNBVf.mjs → _tanstack-start-manifest_v-Bpe5ZC56.mjs} +1 -1
- package/.output/server/index.mjs +58 -65
- package/package.json +1 -1
- package/src/components/proxy-viewer/LogEntry.tsx +18 -4
- package/src/components/proxy-viewer/LogEntryHeader.tsx +3 -3
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +292 -0
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +196 -45
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +92 -67
- package/src/components/proxy-viewer/anatomy/types.ts +15 -13
- package/src/components/proxy-viewer/log-formats/anthropic.ts +1 -1
- package/src/components/proxy-viewer/log-formats/openai.ts +1 -1
- package/src/components/proxy-viewer/log-formats/types.ts +1 -1
- package/src/components/proxy-viewer/requestTools.ts +213 -0
- package/src/components/ui/json-viewer.tsx +1 -1
- package/.output/public/assets/CompareDrawer-3nRwtk8J.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-CbW5VRER.js +0 -101
- package/.output/public/assets/ReplayDialog-Cl62N9PI.js +0 -1
- package/.output/public/assets/RequestAnatomy-DgQWGvjs.js +0 -1
- package/.output/public/assets/ResponseView-Cvc-ct4E.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-BCQaCAIe.js +0 -1
- package/.output/public/assets/_sessionId-CcD_aLGq.js +0 -1
- package/.output/public/assets/index-B_dffD3u.js +0 -1
- package/.output/public/assets/index-CX796gvi.css +0 -1
- package/.output/public/assets/json-viewer-IXejqXB0.js +0 -14
- package/.output/server/_ssr/RequestAnatomy-DS2tZOgq.mjs +0 -353
- package/.output/server/_ssr/json-viewer-DDU55MLK.mjs +0 -515
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type JSX, memo, useMemo } from "react";
|
|
2
2
|
import { cn, formatTokens } from "../../../lib/utils";
|
|
3
3
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
|
|
4
|
-
import type
|
|
4
|
+
import { ANATOMY_ROLE_LABELS, type AnatomyRole, type AnatomySegment } from "./types";
|
|
5
5
|
|
|
6
|
-
const ROLE_COLOR_CLASSES: Record<AnatomyRole, string> = {
|
|
6
|
+
export const ROLE_COLOR_CLASSES: Record<AnatomyRole, string> = {
|
|
7
7
|
system: "bg-sky-500/70",
|
|
8
8
|
user: "bg-emerald-500/70",
|
|
9
9
|
assistant: "bg-violet-500/70",
|
|
@@ -21,59 +21,69 @@ const ROLE_FOCUS_RING: Record<AnatomyRole, string> = {
|
|
|
21
21
|
|
|
22
22
|
const MAX_VISIBLE_SEGMENTS = 12;
|
|
23
23
|
const MIN_SEGMENT_PERCENT = 1;
|
|
24
|
-
|
|
25
24
|
const TOOLTIP_PREVIEW_LIMIT = 80;
|
|
26
25
|
const LABEL_TRUNCATE_LIMIT = 24;
|
|
27
26
|
|
|
28
27
|
function truncateLabel(label: string): string {
|
|
29
28
|
if (label.length <= LABEL_TRUNCATE_LIMIT) return label;
|
|
30
|
-
return `${label.slice(0, LABEL_TRUNCATE_LIMIT -
|
|
29
|
+
return `${label.slice(0, LABEL_TRUNCATE_LIMIT - 3)}...`;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
function truncatePreview(text: string): string {
|
|
34
33
|
const singleLine = text.replace(/\s+/g, " ").trim();
|
|
35
34
|
if (singleLine.length <= TOOLTIP_PREVIEW_LIMIT) return singleLine;
|
|
36
|
-
return `${singleLine.slice(0, TOOLTIP_PREVIEW_LIMIT)}
|
|
35
|
+
return `${singleLine.slice(0, TOOLTIP_PREVIEW_LIMIT)}...`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatPercent(value: number): string {
|
|
39
|
+
if (value >= 10) return `${value.toFixed(0)}%`;
|
|
40
|
+
if (value >= 1) return `${value.toFixed(1)}%`;
|
|
41
|
+
if (value > 0) return "<1%";
|
|
42
|
+
return "0%";
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export type SegmentBarProps = {
|
|
40
46
|
segments: ReadonlyArray<AnatomySegment>;
|
|
41
47
|
totalTokens: number;
|
|
48
|
+
showLabels?: boolean;
|
|
42
49
|
onActivate?: (segment: AnatomySegment) => void;
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
* Render a horizontal stacked bar showing the relative size of each
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* cap. Click or keyboard activation (Enter / Space) calls `onActivate`.
|
|
53
|
+
* Render a horizontal stacked bar showing the relative size of each request
|
|
54
|
+
* context segment. Segment mode can activate a JSON path; aggregate modes stay
|
|
55
|
+
* static so they do not imply a misleading jump target.
|
|
50
56
|
*/
|
|
51
57
|
export const SegmentBar = memo(function SegmentBar({
|
|
52
58
|
segments,
|
|
53
59
|
totalTokens,
|
|
60
|
+
showLabels = true,
|
|
54
61
|
onActivate,
|
|
55
62
|
}: SegmentBarProps): JSX.Element {
|
|
56
63
|
const total = useMemo(() => {
|
|
57
64
|
if (totalTokens > 0) return totalTokens;
|
|
58
|
-
return segments.reduce((sum,
|
|
65
|
+
return segments.reduce((sum, segment) => sum + segment.size, 0);
|
|
59
66
|
}, [segments, totalTokens]);
|
|
60
67
|
|
|
61
68
|
const visibleSegments = segments.slice(0, MAX_VISIBLE_SEGMENTS);
|
|
62
69
|
const overflowSegments = segments.slice(MAX_VISIBLE_SEGMENTS);
|
|
63
|
-
const overflowSize = overflowSegments.reduce((sum,
|
|
70
|
+
const overflowSize = overflowSegments.reduce((sum, segment) => sum + segment.size, 0);
|
|
64
71
|
const overflowCount = overflowSegments.length;
|
|
65
72
|
const overflowStartIndex = MAX_VISIBLE_SEGMENTS;
|
|
66
73
|
const overflowEndIndex = overflowStartIndex + overflowCount - 1;
|
|
67
74
|
const hasOverflow = overflowCount > 0;
|
|
75
|
+
const interactive = onActivate !== undefined;
|
|
68
76
|
|
|
69
77
|
const visibleTotal = useMemo(
|
|
70
|
-
() => visibleSegments.reduce((sum,
|
|
78
|
+
() => visibleSegments.reduce((sum, segment) => sum + segment.size, 0),
|
|
71
79
|
[visibleSegments],
|
|
72
80
|
);
|
|
73
81
|
|
|
74
82
|
const ariaLabel = useMemo(
|
|
75
83
|
() =>
|
|
76
|
-
`Request
|
|
84
|
+
`Request context: ~${formatTokens(total)} tokens across ${segments.length} segment${
|
|
85
|
+
segments.length === 1 ? "" : "s"
|
|
86
|
+
}`,
|
|
77
87
|
[segments.length, total],
|
|
78
88
|
);
|
|
79
89
|
|
|
@@ -91,36 +101,48 @@ export const SegmentBar = memo(function SegmentBar({
|
|
|
91
101
|
{visibleSegments.map((segment, index) => {
|
|
92
102
|
const rawPercent = total > 0 ? (segment.size / total) * 100 : 0;
|
|
93
103
|
const percent = Math.max(MIN_SEGMENT_PERCENT, rawPercent);
|
|
104
|
+
const segmentClassName = cn(
|
|
105
|
+
"h-full border-r border-background/80 last:border-r-0",
|
|
106
|
+
interactive
|
|
107
|
+
? "opacity-90 hover:opacity-100 focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background"
|
|
108
|
+
: "opacity-90",
|
|
109
|
+
ROLE_COLOR_CLASSES[segment.role],
|
|
110
|
+
interactive ? ROLE_FOCUS_RING[segment.role] : "",
|
|
111
|
+
);
|
|
112
|
+
const segmentStyle = { width: `${percent}%` };
|
|
113
|
+
const ariaText = `${segment.label}, ${formatPercent(rawPercent)}, ~${formatTokens(
|
|
114
|
+
segment.size,
|
|
115
|
+
)} tokens`;
|
|
94
116
|
return (
|
|
95
117
|
<Tooltip key={`${segment.path}-${index}`}>
|
|
96
118
|
<TooltipTrigger asChild>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
style={{ width: `${percent}%` }}
|
|
118
|
-
/>
|
|
119
|
+
{interactive ? (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
role="button"
|
|
123
|
+
tabIndex={0}
|
|
124
|
+
onClick={() => onActivate(segment)}
|
|
125
|
+
onKeyDown={(event) => {
|
|
126
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
onActivate(segment);
|
|
129
|
+
}
|
|
130
|
+
}}
|
|
131
|
+
data-anatomy-path={segment.path}
|
|
132
|
+
aria-label={ariaText}
|
|
133
|
+
className={segmentClassName}
|
|
134
|
+
style={segmentStyle}
|
|
135
|
+
/>
|
|
136
|
+
) : (
|
|
137
|
+
<span aria-label={ariaText} className={segmentClassName} style={segmentStyle} />
|
|
138
|
+
)}
|
|
119
139
|
</TooltipTrigger>
|
|
120
140
|
<TooltipContent side="bottom" className="max-w-sm text-xs p-2 space-y-0.5">
|
|
121
141
|
<div className="font-semibold">
|
|
122
|
-
{segment.label}
|
|
142
|
+
{segment.label} - {formatPercent(rawPercent)} - ~{formatTokens(segment.size)}{" "}
|
|
143
|
+
tokens
|
|
123
144
|
</div>
|
|
145
|
+
<div className="text-muted-foreground">{ANATOMY_ROLE_LABELS[segment.role]}</div>
|
|
124
146
|
<div className="text-muted-foreground">
|
|
125
147
|
{segment.characters.toLocaleString()} chars
|
|
126
148
|
</div>
|
|
@@ -144,51 +166,54 @@ export const SegmentBar = memo(function SegmentBar({
|
|
|
144
166
|
width: `${Math.max(MIN_SEGMENT_PERCENT, (overflowSize / total) * 100)}%`,
|
|
145
167
|
}}
|
|
146
168
|
>
|
|
147
|
-
|
|
169
|
+
... +{overflowCount}
|
|
148
170
|
</div>
|
|
149
171
|
</TooltipTrigger>
|
|
150
172
|
<TooltipContent side="bottom" className="text-xs">
|
|
151
173
|
{overflowCount} more segment{overflowCount === 1 ? "" : "s"} (indices{" "}
|
|
152
|
-
{overflowStartIndex}
|
|
174
|
+
{overflowStartIndex}-{overflowEndIndex})
|
|
153
175
|
</TooltipContent>
|
|
154
176
|
</Tooltip>
|
|
155
177
|
)}
|
|
156
178
|
</div>
|
|
157
179
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
{showLabels && (
|
|
181
|
+
<div className="flex w-full gap-1 text-[10px] text-muted-foreground">
|
|
182
|
+
{visibleSegments.map((segment, index) => {
|
|
183
|
+
const rawPercent = total > 0 ? (segment.size / total) * 100 : 0;
|
|
184
|
+
const percent = Math.max(MIN_SEGMENT_PERCENT, rawPercent);
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
key={`label-${segment.path}-${index}`}
|
|
188
|
+
className="flex flex-col gap-0.5 truncate"
|
|
189
|
+
style={{ width: `${percent}%` }}
|
|
190
|
+
title={`${segment.label} - ${formatPercent(rawPercent)} - ~${formatTokens(
|
|
191
|
+
segment.size,
|
|
192
|
+
)} tokens`}
|
|
193
|
+
>
|
|
194
|
+
<span className="truncate font-mono text-foreground/80">
|
|
195
|
+
{truncateLabel(segment.label)}
|
|
196
|
+
</span>
|
|
197
|
+
<span className="truncate font-mono text-muted-foreground/70">
|
|
198
|
+
{formatPercent(rawPercent)} - ~{formatTokens(segment.size)}
|
|
199
|
+
</span>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
{hasOverflow && (
|
|
163
204
|
<div
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
style={{ width: `${percent}%` }}
|
|
167
|
-
title={`${segment.label} · ~${formatTokens(segment.size)} tokens`}
|
|
205
|
+
className="flex flex-col gap-0.5 truncate text-muted-foreground"
|
|
206
|
+
style={{ width: `${Math.max(MIN_SEGMENT_PERCENT, (overflowSize / total) * 100)}%` }}
|
|
168
207
|
>
|
|
169
|
-
<span className="truncate font-mono text-foreground/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<span className="truncate font-mono text-muted-foreground/70">
|
|
173
|
-
~{formatTokens(segment.size)}
|
|
208
|
+
<span className="truncate font-mono text-foreground/60">... +{overflowCount}</span>
|
|
209
|
+
<span className="truncate font-mono text-muted-foreground/60">
|
|
210
|
+
~{formatTokens(overflowSize)}
|
|
174
211
|
</span>
|
|
175
212
|
</div>
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<div
|
|
180
|
-
className="flex flex-col gap-0.5 truncate text-muted-foreground"
|
|
181
|
-
style={{ width: `${Math.max(MIN_SEGMENT_PERCENT, (overflowSize / total) * 100)}%` }}
|
|
182
|
-
>
|
|
183
|
-
<span className="truncate font-mono text-foreground/60">… +{overflowCount}</span>
|
|
184
|
-
<span className="truncate font-mono text-muted-foreground/60">
|
|
185
|
-
~{formatTokens(overflowSize)}
|
|
186
|
-
</span>
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
190
216
|
|
|
191
|
-
{/* Spacer to maintain minimum bar height for tiny segments */}
|
|
192
217
|
{visibleTotal < total * 0.1 && <div className="h-0" />}
|
|
193
218
|
</div>
|
|
194
219
|
</TooltipProvider>
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Role of a request segment. Drives the segment's color in the
|
|
2
|
+
* Role of a request segment. Drives the segment's color in the context bar.
|
|
3
3
|
*
|
|
4
4
|
* - `system`: the system prompt (Anthropic `system`, or leading system-role message in OpenAI)
|
|
5
5
|
* - `user`: a user-role message
|
|
6
6
|
* - `assistant`: an assistant-role message
|
|
7
|
-
* - `tool`: a tool-role message (OpenAI only
|
|
8
|
-
* - `tools`: the synthetic
|
|
7
|
+
* - `tool`: a tool-role message (OpenAI only; Anthropic uses tool_result blocks inside user messages)
|
|
8
|
+
* - `tools`: the synthetic tool definitions segment that aggregates the `tools` array
|
|
9
9
|
*/
|
|
10
10
|
export type AnatomyRole = "system" | "user" | "assistant" | "tool" | "tools";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* A single segment of a request as visualized in the
|
|
13
|
+
* A single segment of a request as visualized in the request context tab.
|
|
14
14
|
*
|
|
15
15
|
* `path` is a JSON-pointer-style path into the parsed body
|
|
16
16
|
* (e.g. `/system`, `/messages/0`, `/tools`). The path is opaque to
|
|
17
|
-
* consumers
|
|
18
|
-
*
|
|
19
|
-
* `JsonViewer`. A `null` path is allowed for the synthetic `tools`
|
|
20
|
-
* segment in the rare case where the tools array cannot be located.
|
|
17
|
+
* consumers: it is matched against the same path emitted by the request
|
|
18
|
+
* analyzer and is used to drive click-to-jump in the `JsonViewer`.
|
|
21
19
|
*/
|
|
22
20
|
export type AnatomySegment = {
|
|
23
21
|
/** Stable role used for color and label prefix. */
|
|
@@ -30,10 +28,14 @@ export type AnatomySegment = {
|
|
|
30
28
|
characters: number;
|
|
31
29
|
/** Raw text of the segment used to compute `size`. Used in the tooltip preview. */
|
|
32
30
|
text: string;
|
|
33
|
-
/**
|
|
34
|
-
* JSON-pointer-style path into the parsed body (e.g. `/messages/3`).
|
|
35
|
-
* `null` when the segment does not correspond to a single node
|
|
36
|
-
* (e.g. the synthetic `tools` segment that aggregates the array).
|
|
37
|
-
*/
|
|
31
|
+
/** JSON-pointer-style path into the parsed body (e.g. `/messages/3`). */
|
|
38
32
|
path: string;
|
|
39
33
|
};
|
|
34
|
+
|
|
35
|
+
export const ANATOMY_ROLE_LABELS: Record<AnatomyRole, string> = {
|
|
36
|
+
system: "System",
|
|
37
|
+
user: "User",
|
|
38
|
+
assistant: "Assistant",
|
|
39
|
+
tool: "Tool Results",
|
|
40
|
+
tools: "Tool Definitions",
|
|
41
|
+
};
|
|
@@ -155,7 +155,7 @@ export const anthropicLogFormatAdapter: LogFormatAdapter = {
|
|
|
155
155
|
if (parsed === null || typeof parsed !== "object") return null;
|
|
156
156
|
// We deliberately skip AnthropicRequestSchema validation here:
|
|
157
157
|
// real Anthropic requests accept `system` as either a string or
|
|
158
|
-
// an array, and we want the
|
|
158
|
+
// an array, and we want the Context view to render even when the
|
|
159
159
|
// body shape is slightly off-schema (e.g. system: "string" vs
|
|
160
160
|
// system: [{type:"text",text:"..."}]).
|
|
161
161
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -128,7 +128,7 @@ export const openAILogFormatAdapter: LogFormatAdapter = {
|
|
|
128
128
|
anatomySegments(parsed) {
|
|
129
129
|
if (parsed === null || typeof parsed !== "object") return null;
|
|
130
130
|
// We deliberately skip OpenAIRequestSchema validation here for the
|
|
131
|
-
// same reason as the Anthropic adapter: the
|
|
131
|
+
// same reason as the Anthropic adapter: the Context view should
|
|
132
132
|
// render even when the body shape is slightly off-schema. We only
|
|
133
133
|
// need the top-level `messages` and `tools` arrays.
|
|
134
134
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -18,7 +18,7 @@ export type LogFormatAdapter = {
|
|
|
18
18
|
analyzeRequest(rawBody: string | null): RequestAnalysis;
|
|
19
19
|
analyzeResponse(responseText: string | null): ResponseAnalysis;
|
|
20
20
|
/**
|
|
21
|
-
* Derive the ordered list of segments shown in the
|
|
21
|
+
* Derive the ordered list of segments shown in the Context tab for a
|
|
22
22
|
* parsed request body. Returns `null` when the body is `null` or fails
|
|
23
23
|
* the format's schema (e.g. unknown format).
|
|
24
24
|
*/
|
|
@@ -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
|
+
}
|
|
@@ -380,7 +380,7 @@ export type JsonViewerProps = {
|
|
|
380
380
|
bulkRevision?: number;
|
|
381
381
|
/**
|
|
382
382
|
* Set of JSON-pointer-style paths whose row should expose a
|
|
383
|
-
* `data-anatomy-path` attribute. Used by the
|
|
383
|
+
* `data-anatomy-path` attribute. Used by the Context view to
|
|
384
384
|
* locate rows for click-to-jump. When `null` or `undefined`,
|
|
385
385
|
* no `data-anatomy-path` attribute is emitted (zero cost).
|
|
386
386
|
*/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as h,j as t}from"./main-2NlGzgOe.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-CbW5VRER.js";import{J as N}from"./json-viewer-IXejqXB0.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};
|