@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.
Files changed (50) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-BcEd6V-V.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +106 -0
  4. package/.output/public/assets/ReplayDialog-BNpC0548.js +1 -0
  5. package/.output/public/assets/{RequestAnatomy-BNahe83D.js → RequestAnatomy-Ds1uRLVB.js} +1 -1
  6. package/.output/public/assets/{ResponseView-DSOnGqi6.js → ResponseView-7KPVqKl5.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-BEKTDklB.js → StreamingChunkSequence-BHQT261s.js} +1 -1
  8. package/.output/public/assets/_sessionId-DWePGjnS.js +1 -0
  9. package/.output/public/assets/index-CI1-G8ua.js +1 -0
  10. package/.output/public/assets/index-DdhFqPsI.css +1 -0
  11. package/.output/public/assets/{index-DWOkqdCa.js → index-DjKt8XKe.js} +1 -1
  12. package/.output/public/assets/{json-viewer-C2JpgcW0.js → json-viewer-CkCu-rka.js} +1 -1
  13. package/.output/public/assets/{main-CSONBwwn.js → main-DpD1N0S8.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +194 -200
  15. package/.output/server/{_sessionId-CPAa37n5.mjs → _sessionId-DF9Sy8cP.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-ceW5VxMo.mjs → CompareDrawer-BoxztaO7.mjs} +36 -15
  17. package/.output/server/_ssr/{ProxyViewerContainer-CDfEE_w-.mjs → ProxyViewerContainer-CRBkqFlJ.mjs} +362 -124
  18. package/.output/server/_ssr/{ReplayDialog-V0s_eEbR.mjs → ReplayDialog-Cc1dyDuK.mjs} +14 -7
  19. package/.output/server/_ssr/{RequestAnatomy-f1ccwR9d.mjs → RequestAnatomy-CMGSsz5Z.mjs} +2 -2
  20. package/.output/server/_ssr/{ResponseView-BIRrqG4H.mjs → ResponseView-Cp10DM1D.mjs} +3 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-V3JFjCgX.mjs → StreamingChunkSequence-B1VGxy3A.mjs} +2 -2
  22. package/.output/server/_ssr/{index-DsykulzS.mjs → index-47XVPghS.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-Dcnm0Ivf.mjs → json-viewer-zDE2rrmJ.mjs} +3 -3
  25. package/.output/server/_ssr/{router-4bdm6Mt2.mjs → router-DVeuZFqI.mjs} +10 -5
  26. package/.output/server/{_tanstack-start-manifest_v-Xp4CO64V.mjs → _tanstack-start-manifest_v-Bp8JxtPW.mjs} +1 -1
  27. package/.output/server/index.mjs +68 -68
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +28 -2
  30. package/src/components/clients/ClientLogo.tsx +132 -0
  31. package/src/components/groups/GroupsDialog.tsx +18 -13
  32. package/src/components/proxy-viewer/AgentTraceSummary.tsx +56 -23
  33. package/src/components/proxy-viewer/CompareDrawer.tsx +38 -8
  34. package/src/components/proxy-viewer/ConversationHeader.tsx +33 -41
  35. package/src/components/proxy-viewer/LogEntry.tsx +10 -1
  36. package/src/components/proxy-viewer/LogEntryHeader.tsx +14 -14
  37. package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
  38. package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
  39. package/src/components/proxy-viewer/ThreadConnector.tsx +18 -7
  40. package/src/components/proxy-viewer/TurnGroup.tsx +14 -25
  41. package/src/components/proxy-viewer/viewerState.ts +5 -2
  42. package/src/lib/sessionInfoContract.ts +1 -0
  43. package/src/lib/stopReason.ts +48 -16
  44. package/src/proxy/sessionInfo.ts +5 -1
  45. package/.output/public/assets/CompareDrawer-DjgjIFx7.js +0 -1
  46. package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +0 -106
  47. package/.output/public/assets/ReplayDialog-CU0Tbb2c.js +0 -1
  48. package/.output/public/assets/_sessionId-Cif8JZdn.js +0 -1
  49. package/.output/public/assets/index-D_WfwzUi.js +0 -1
  50. 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 { StopReason } from "../../lib/stopReason";
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 `#id` index regardless of the
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 === "end_turn" || stopReason === "stop";
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 className="text-blue-400/80 font-mono font-semibold tabular-nums shrink-0">
307
- #{entries[0]?.log.id ?? "?"} ~ #{entries[lastIdx]?.log.id ?? "?"}
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 (const log of logs) {
89
- entries.push({ log, stopReason: extractStopReason(log) });
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(),
@@ -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
- if (json.stop_reason === "end_turn" || json.stop_reason === "tool_use") {
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
- Array.isArray(json.choices) &&
38
- json.choices.length > 0 &&
39
- isRecord(json.choices[0]) &&
40
- typeof json.choices[0].finish_reason === "string" &&
41
- json.choices[0].finish_reason === "stop"
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 naturally (Anthropic end_turn or OpenAI stop).
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
  }
@@ -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: summaries.slice(0, latestLogLimit),
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};