@tonyclaw/agent-inspector 2.1.14 → 2.1.15
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-BcEd6V-V.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +106 -0
- package/.output/public/assets/ReplayDialog-BNpC0548.js +1 -0
- package/.output/public/assets/{RequestAnatomy-BNahe83D.js → RequestAnatomy-Ds1uRLVB.js} +1 -1
- package/.output/public/assets/{ResponseView-DSOnGqi6.js → ResponseView-7KPVqKl5.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BEKTDklB.js → StreamingChunkSequence-BHQT261s.js} +1 -1
- package/.output/public/assets/_sessionId-DWePGjnS.js +1 -0
- package/.output/public/assets/index-CI1-G8ua.js +1 -0
- package/.output/public/assets/index-DdhFqPsI.css +1 -0
- package/.output/public/assets/{index-DWOkqdCa.js → index-DjKt8XKe.js} +1 -1
- package/.output/public/assets/{json-viewer-C2JpgcW0.js → json-viewer-CkCu-rka.js} +1 -1
- package/.output/public/assets/{main-CSONBwwn.js → main-DpD1N0S8.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +194 -200
- package/.output/server/{_sessionId-CPAa37n5.mjs → _sessionId-DF9Sy8cP.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-ceW5VxMo.mjs → CompareDrawer-BoxztaO7.mjs} +36 -15
- package/.output/server/_ssr/{ProxyViewerContainer-CDfEE_w-.mjs → ProxyViewerContainer-CRBkqFlJ.mjs} +362 -124
- package/.output/server/_ssr/{ReplayDialog-V0s_eEbR.mjs → ReplayDialog-Cc1dyDuK.mjs} +14 -7
- package/.output/server/_ssr/{RequestAnatomy-f1ccwR9d.mjs → RequestAnatomy-CMGSsz5Z.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-BIRrqG4H.mjs → ResponseView-Cp10DM1D.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-V3JFjCgX.mjs → StreamingChunkSequence-B1VGxy3A.mjs} +2 -2
- package/.output/server/_ssr/{index-DsykulzS.mjs → index-47XVPghS.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Dcnm0Ivf.mjs → json-viewer-zDE2rrmJ.mjs} +3 -3
- package/.output/server/_ssr/{router-4bdm6Mt2.mjs → router-DVeuZFqI.mjs} +10 -5
- package/.output/server/{_tanstack-start-manifest_v-Xp4CO64V.mjs → _tanstack-start-manifest_v-Bp8JxtPW.mjs} +1 -1
- package/.output/server/index.mjs +68 -68
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +28 -2
- package/src/components/clients/ClientLogo.tsx +132 -0
- package/src/components/groups/GroupsDialog.tsx +18 -13
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +56 -23
- package/src/components/proxy-viewer/CompareDrawer.tsx +38 -8
- package/src/components/proxy-viewer/ConversationHeader.tsx +33 -41
- package/src/components/proxy-viewer/LogEntry.tsx +10 -1
- package/src/components/proxy-viewer/LogEntryHeader.tsx +14 -14
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
- package/src/components/proxy-viewer/ThreadConnector.tsx +18 -7
- package/src/components/proxy-viewer/TurnGroup.tsx +14 -25
- package/src/components/proxy-viewer/viewerState.ts +5 -2
- package/src/lib/sessionInfoContract.ts +1 -0
- package/src/lib/stopReason.ts +48 -16
- package/src/proxy/sessionInfo.ts +5 -1
- package/.output/public/assets/CompareDrawer-DjgjIFx7.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +0 -106
- package/.output/public/assets/ReplayDialog-CU0Tbb2c.js +0 -1
- package/.output/public/assets/_sessionId-Cif8JZdn.js +0 -1
- package/.output/public/assets/index-D_WfwzUi.js +0 -1
- package/.output/public/assets/index-DtLuQrs0.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type JSX, useMemo } from "react";
|
|
2
2
|
import { cn } from "../../lib/utils";
|
|
3
|
-
import type
|
|
3
|
+
import { isTurnBoundary, type StopReason } from "../../lib/stopReason";
|
|
4
4
|
import { getCrabVariant, getInteriorCrabVariant } from "../ui/crab-variants";
|
|
5
5
|
|
|
6
6
|
export type ThreadConnectorProps = {
|
|
@@ -18,10 +18,24 @@ export type ThreadConnectorProps = {
|
|
|
18
18
|
onToggle?: () => void;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
function boundaryTitle(stopReason: StopReason): string {
|
|
22
|
+
switch (stopReason) {
|
|
23
|
+
case "end_turn":
|
|
24
|
+
return "End of Turn (Anthropic)";
|
|
25
|
+
case "stop":
|
|
26
|
+
return "End of Turn (OpenAI)";
|
|
27
|
+
case "length":
|
|
28
|
+
return "End of Turn (length limit)";
|
|
29
|
+
case "tool_use":
|
|
30
|
+
case null:
|
|
31
|
+
return "End of Turn";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/**
|
|
22
36
|
* Vertical timeline connector. Top spacer uses a calc() that
|
|
23
37
|
* mixes rem (py-1 + half-line) and px (border) so the crab
|
|
24
|
-
* centre-line matches the LogEntry
|
|
38
|
+
* centre-line matches the LogEntry display number regardless of the
|
|
25
39
|
* root font-size. The bottom spacer is flex-1 so the outgoing
|
|
26
40
|
* line adapts to whatever height the LogEntry occupies.
|
|
27
41
|
*/
|
|
@@ -35,7 +49,7 @@ export function ThreadConnector({
|
|
|
35
49
|
collapsible = false,
|
|
36
50
|
onToggle,
|
|
37
51
|
}: ThreadConnectorProps): JSX.Element {
|
|
38
|
-
const isBoundary = stopReason
|
|
52
|
+
const isBoundary = isTurnBoundary(stopReason);
|
|
39
53
|
const isFusedBoundary = isOnlyEntry && isTurnStart && isBoundary;
|
|
40
54
|
const isRunning = isPending && !isBoundary;
|
|
41
55
|
const Crab = useMemo(() => getCrabVariant(crabIndex), [crabIndex]);
|
|
@@ -84,10 +98,7 @@ export function ThreadConnector({
|
|
|
84
98
|
/>
|
|
85
99
|
</span>
|
|
86
100
|
) : isBoundary ? (
|
|
87
|
-
<span
|
|
88
|
-
title={stopReason === "end_turn" ? "End of Turn (Anthropic)" : "End of Turn (OpenAI)"}
|
|
89
|
-
{...interactiveProps}
|
|
90
|
-
>
|
|
101
|
+
<span title={boundaryTitle(stopReason)} {...interactiveProps}>
|
|
91
102
|
<Crab
|
|
92
103
|
className={cn(
|
|
93
104
|
"size-3.5 text-amber-400",
|
|
@@ -14,8 +14,8 @@ import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
|
14
14
|
import { cn, formatTokens } from "../../lib/utils";
|
|
15
15
|
import type { CapturedLog } from "../../contracts";
|
|
16
16
|
import { getCrabVariant } from "../ui/crab-variants";
|
|
17
|
-
import { ProviderLogo, detectProvider, type Provider } from "../providers/ProviderLogo";
|
|
18
17
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
|
|
18
|
+
import { ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
|
|
19
19
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
20
20
|
import { LogEntry } from "./LogEntry";
|
|
21
21
|
import {
|
|
@@ -142,16 +142,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
142
142
|
};
|
|
143
143
|
}, [entries, lastIdx]);
|
|
144
144
|
|
|
145
|
-
// Unique providers across all entries (for collapsed model logos)
|
|
146
|
-
const uniqueProviders = useMemo(() => {
|
|
147
|
-
const seen = new Set<Provider>();
|
|
148
|
-
for (const e of entries) {
|
|
149
|
-
const p = detectProvider(e.log.model);
|
|
150
|
-
if (p !== "unknown") seen.add(p);
|
|
151
|
-
}
|
|
152
|
-
return [...seen];
|
|
153
|
-
}, [entries]);
|
|
154
|
-
|
|
155
145
|
// Crab variant creators for the dual-crab collapsed layout
|
|
156
146
|
const StartCrab = useMemo(() => getCrabVariant(entries[0]?.log.id ?? 0), [entries]);
|
|
157
147
|
const EndCrab = useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
|
|
@@ -302,9 +292,19 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
302
292
|
bgClass,
|
|
303
293
|
)}
|
|
304
294
|
>
|
|
295
|
+
<span className={ROW_CHEVRON_SLOT_CLASS}>
|
|
296
|
+
<ChevronRight className="size-4 shrink-0" />
|
|
297
|
+
</span>
|
|
298
|
+
|
|
305
299
|
{/* ID range */}
|
|
306
|
-
<span
|
|
307
|
-
|
|
300
|
+
<span
|
|
301
|
+
className="text-blue-400/80 font-mono font-semibold tabular-nums shrink-0"
|
|
302
|
+
title={`Log IDs ${String(entries[0]?.log.id ?? "?")} ~ ${String(
|
|
303
|
+
entries[lastIdx]?.log.id ?? "?",
|
|
304
|
+
)}`}
|
|
305
|
+
>
|
|
306
|
+
#{entries[0]?.sessionLogNumber ?? "?"} ~ #
|
|
307
|
+
{entries[lastIdx]?.sessionLogNumber ?? "?"}
|
|
308
308
|
</span>
|
|
309
309
|
|
|
310
310
|
{/* Request count */}
|
|
@@ -312,15 +312,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
312
312
|
{entries.length} request{entries.length > 1 ? "s" : ""}
|
|
313
313
|
</span>
|
|
314
314
|
|
|
315
|
-
{/* Model logos — one per unique provider */}
|
|
316
|
-
{uniqueProviders.length > 0 && (
|
|
317
|
-
<span className="flex items-center gap-0.5 shrink-0">
|
|
318
|
-
{uniqueProviders.map((p) => (
|
|
319
|
-
<ProviderLogo key={p} provider={p} className="size-4" />
|
|
320
|
-
))}
|
|
321
|
-
</span>
|
|
322
|
-
)}
|
|
323
|
-
|
|
324
315
|
{/* Elapsed — slowest single request in the turn (not the sum) */}
|
|
325
316
|
{aggregate.maxElapsed !== null && (
|
|
326
317
|
<TooltipProvider>
|
|
@@ -368,9 +359,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
368
359
|
|
|
369
360
|
{/* Spacer */}
|
|
370
361
|
<span className="flex-1 min-w-0" />
|
|
371
|
-
|
|
372
|
-
{/* Expand chevron */}
|
|
373
|
-
<ChevronRight className="size-4 text-muted-foreground shrink-0" />
|
|
374
362
|
</div>
|
|
375
363
|
)}
|
|
376
364
|
</div>
|
|
@@ -404,6 +392,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
404
392
|
<div className={cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass)}>
|
|
405
393
|
<LogEntry
|
|
406
394
|
log={log}
|
|
395
|
+
displayNumber={entry.sessionLogNumber}
|
|
407
396
|
viewMode={viewMode}
|
|
408
397
|
strip={strip}
|
|
409
398
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
@@ -6,6 +6,7 @@ import { resolveLogFormat } from "./log-formats";
|
|
|
6
6
|
export type TurnEntry = {
|
|
7
7
|
log: CapturedLog;
|
|
8
8
|
stopReason: StopReason;
|
|
9
|
+
sessionLogNumber: number;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
export type TurnGroupData = {
|
|
@@ -85,8 +86,10 @@ export function buildTurnGroups(logs: CapturedLog[]): TurnGroupData[] {
|
|
|
85
86
|
let entries: TurnEntry[] = [];
|
|
86
87
|
let turnIndex = 0;
|
|
87
88
|
|
|
88
|
-
for (
|
|
89
|
-
|
|
89
|
+
for (let index = 0; index < logs.length; index += 1) {
|
|
90
|
+
const log = logs[index];
|
|
91
|
+
if (log === undefined) continue;
|
|
92
|
+
entries.push({ log, stopReason: extractStopReason(log), sessionLogNumber: index + 1 });
|
|
90
93
|
const current = entries[entries.length - 1];
|
|
91
94
|
if (current !== undefined && isTurnBoundary(current.stopReason)) {
|
|
92
95
|
groups.push({ entries, turnIndex });
|
|
@@ -10,6 +10,7 @@ export const SessionTokenUsageSchema = z.object({
|
|
|
10
10
|
|
|
11
11
|
export const SessionLogSummarySchema = z.object({
|
|
12
12
|
id: z.number().int().positive(),
|
|
13
|
+
sessionLogNumber: z.number().int().positive().optional(),
|
|
13
14
|
timestamp: z.string(),
|
|
14
15
|
provider: z.string().nullable(),
|
|
15
16
|
model: z.string().nullable(),
|
package/src/lib/stopReason.ts
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import type { CapturedLog } from "../contracts";
|
|
2
2
|
|
|
3
|
-
export type StopReason = "end_turn" | "tool_use" | "stop" | null;
|
|
3
|
+
export type StopReason = "end_turn" | "tool_use" | "stop" | "length" | null;
|
|
4
4
|
|
|
5
5
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
6
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function normalizeAnthropicStopReason(value: string): StopReason {
|
|
10
|
+
switch (value) {
|
|
11
|
+
case "end_turn":
|
|
12
|
+
case "tool_use":
|
|
13
|
+
return value;
|
|
14
|
+
default:
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeOpenAIFinishReason(value: string): StopReason {
|
|
20
|
+
switch (value) {
|
|
21
|
+
case "stop":
|
|
22
|
+
case "length":
|
|
23
|
+
return value;
|
|
24
|
+
default:
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isResponsesLengthLimitReason(value: string): boolean {
|
|
30
|
+
switch (value) {
|
|
31
|
+
case "max_output_tokens":
|
|
32
|
+
case "max_tokens":
|
|
33
|
+
return true;
|
|
34
|
+
default:
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
9
39
|
/**
|
|
10
40
|
* Extracts the stop/finish reason from a captured log's response text.
|
|
11
41
|
* Detects the response body shape (Anthropic or OpenAI) independently of
|
|
@@ -26,21 +56,16 @@ export function extractStopReason(log: CapturedLog): StopReason {
|
|
|
26
56
|
|
|
27
57
|
// Anthropic shape: { stop_reason: "end_turn" | "tool_use" | ... }
|
|
28
58
|
if (typeof json.stop_reason === "string") {
|
|
29
|
-
|
|
30
|
-
return json.stop_reason;
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
59
|
+
return normalizeAnthropicStopReason(json.stop_reason);
|
|
33
60
|
}
|
|
34
61
|
|
|
35
|
-
// OpenAI shape: { choices: [{ finish_reason: "stop" | ... }] }
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
) {
|
|
43
|
-
return "stop";
|
|
62
|
+
// OpenAI Chat shape: { choices: [{ finish_reason: "stop" | "length" | ... }] }
|
|
63
|
+
if (Array.isArray(json.choices)) {
|
|
64
|
+
for (const choice of json.choices) {
|
|
65
|
+
if (!isRecord(choice) || typeof choice.finish_reason !== "string") continue;
|
|
66
|
+
const reason = normalizeOpenAIFinishReason(choice.finish_reason);
|
|
67
|
+
if (reason !== null) return reason;
|
|
68
|
+
}
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
// OpenAI Responses shape: { status: "completed", output: [...] }
|
|
@@ -55,6 +80,13 @@ export function extractStopReason(log: CapturedLog): StopReason {
|
|
|
55
80
|
}
|
|
56
81
|
}
|
|
57
82
|
|
|
83
|
+
if (json.status === "incomplete" && isRecord(json.incomplete_details)) {
|
|
84
|
+
const reason = json.incomplete_details.reason;
|
|
85
|
+
if (typeof reason === "string" && isResponsesLengthLimitReason(reason)) {
|
|
86
|
+
return "length";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
return null;
|
|
59
91
|
} catch {
|
|
60
92
|
return null;
|
|
@@ -63,8 +95,8 @@ export function extractStopReason(log: CapturedLog): StopReason {
|
|
|
63
95
|
|
|
64
96
|
/**
|
|
65
97
|
* Returns true when the stop reason indicates the assistant completed its
|
|
66
|
-
* turn
|
|
98
|
+
* turn, including responses truncated by output length limits.
|
|
67
99
|
*/
|
|
68
100
|
export function isTurnBoundary(stopReason: StopReason): boolean {
|
|
69
|
-
return stopReason === "end_turn" || stopReason === "stop";
|
|
101
|
+
return stopReason === "end_turn" || stopReason === "stop" || stopReason === "length";
|
|
70
102
|
}
|
package/src/proxy/sessionInfo.ts
CHANGED
|
@@ -180,6 +180,10 @@ export function buildSessionInfo(input: BuildSessionInfoInput): SessionInfo {
|
|
|
180
180
|
const inspectorPath = getSessionPath(input.sessionId);
|
|
181
181
|
const apiPath = buildApiPath(input.sessionId);
|
|
182
182
|
const compactLogsPath = buildCompactLogsPath(input.sessionId);
|
|
183
|
+
const latestLogs = summaries.slice(0, latestLogLimit).map((summary, index) => ({
|
|
184
|
+
...summary,
|
|
185
|
+
sessionLogNumber: Math.max(1, requestCount - index),
|
|
186
|
+
}));
|
|
183
187
|
|
|
184
188
|
return {
|
|
185
189
|
id: input.sessionId,
|
|
@@ -220,6 +224,6 @@ export function buildSessionInfo(input: BuildSessionInfoInput): SessionInfo {
|
|
|
220
224
|
compactLogsPath,
|
|
221
225
|
compactLogsUrl: buildAbsoluteUrl(input.baseUrl, compactLogsPath),
|
|
222
226
|
latestLogLimit,
|
|
223
|
-
latestLogs
|
|
227
|
+
latestLogs,
|
|
224
228
|
};
|
|
225
229
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as h,j as t}from"./main-CSONBwwn.js";import{c as X,g as I,r as P,a as q,X as Y,b as m,B as Z,f as $,R as ee,C as te,M as J,d as V,e as _,h as M,i as re,j as ne,k as se,L as ae}from"./ProxyViewerContainer-CWUQZLYy.js";const oe=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],de=X("equal",oe),ie="";function j(e){if(e.length===0)return ie;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 le(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(le(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 ce(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(S(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),x=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!x.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 x=r.value[a],i=n.value[a];x===void 0||i===void 0||R([...e,a],x,i,s)}for(let a=o;a<n.value.length;a++){const x=n.value[a];x!==void 0&&s.push({kind:"added",path:j([...e,a]),value:x})}for(let a=o;a<r.value.length;a++){const x=r.value[a];x!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:x})}}}function S(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||!S(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||!S(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 N(e,r=2){return JSON.stringify(E(e),null,r)}function E(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(E);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=E(s);return r}}}function w({text:e,defaultExpandDepth:r}){return t.jsx(h.Suspense,{fallback:t.jsx("div",{className:"text-xs text-muted-foreground",children:"Loading JSON..."}),children:t.jsx(ae,{text:e,defaultExpandDepth:r})})}function L(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 K(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function ue(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&K(s)){const d=L(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!K(a)||L(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let x=n;x<o;x++){const i=e[x];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:V,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:J,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:_,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:de,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function xe({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,x=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:m("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(M,{className:m("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:m("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:x}),t.jsx("span",{className:m("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(w,{text:N(p.value),defaultExpandDepth:0})]},p.path))})]})}function me({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=B[e.kind],x=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:m("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:m("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(M,{className:m("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(x,{className:m("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:m("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:m("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:m("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(fe,{op:e}):null})]})}function fe({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(w,{text:N(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(w,{text:N(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(w,{text:N(e.right),defaultExpandDepth:0})]})]})}function pe({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:m("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(J,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:m("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(V,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:m("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(_,{className:"size-3"}),e.changed," changed"]})]})}function he({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:m("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:m("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:m("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 ke({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=I(P(e)).analyzeRequest(e.rawRequestBody),c=I(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return ce(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>ue(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,x]=h.useState(new Set),i=l=>{x(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=>{se(l).then(c=>{c&&(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(O=>O.kind==="single"&&O.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:m("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(he,{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(pe,{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(xe,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(me,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(be,{grouped:d,left:e,right:r})})]})]})]})}function be({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{ke as CompareDrawer};
|