@tonyclaw/agent-inspector 2.0.9 → 2.0.11

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 (43) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Cb4RH049.js → CompareDrawer-C9OWdxHM.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-CNzay4u8.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-B3y8E7IE.js → ReplayDialog-CwE6I1Pe.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-DEehC3yz.js +1 -0
  6. package/.output/public/assets/ResponseView-CE5UsuUq.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-CLcLZ7-W.js +1 -0
  8. package/.output/public/assets/_sessionId-DE__tPTo.js +1 -0
  9. package/.output/public/assets/index-BFO4Jmw5.js +1 -0
  10. package/.output/public/assets/index-CDzZ7l_S.css +1 -0
  11. package/.output/public/assets/{main-Bww0Bc88.js → main-D1Xanzu7.js} +2 -2
  12. package/.output/server/{_sessionId-BnCF8Ito.mjs → _sessionId-D3IyPpws.mjs} +3 -3
  13. package/.output/server/_ssr/{CompareDrawer-jPU14EI3.mjs → CompareDrawer-BIMThqxy.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-xesEFppM.mjs → ProxyViewerContainer-BiBdKWtj.mjs} +159 -14
  15. package/.output/server/_ssr/{ReplayDialog-DQvW2zoW.mjs → ReplayDialog-D_wiDyM2.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-BilEphjP.mjs → RequestAnatomy-5UiMTESr.mjs} +399 -44
  17. package/.output/server/_ssr/{ResponseView-DB5PPcWf.mjs → ResponseView-Drk5ghmL.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-Dnrgn2AP.mjs → StreamingChunkSequence-F889rmG0.mjs} +2 -2
  19. package/.output/server/_ssr/{index-YPp821bH.mjs → index-CaPniq9z.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-BvuK0vQv.mjs → router-C7S8FRth.mjs} +531 -111
  22. package/.output/server/{_tanstack-start-manifest_v-DGw3vVOm.mjs → _tanstack-start-manifest_v-CVTkFOdT.mjs} +1 -1
  23. package/.output/server/index.mjs +59 -59
  24. package/package.json +1 -1
  25. package/src/components/providers/ProviderCard.tsx +59 -5
  26. package/src/components/providers/ProviderForm.tsx +42 -0
  27. package/src/components/providers/ProvidersPanel.tsx +68 -0
  28. package/src/components/proxy-viewer/LogEntry.tsx +6 -1
  29. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +179 -4
  30. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +346 -41
  31. package/src/lib/providerContract.ts +12 -0
  32. package/src/lib/providerModelMetadata.ts +327 -0
  33. package/src/proxy/providers.ts +112 -52
  34. package/src/routes/api/providers.$providerId.model-metadata.ts +134 -0
  35. package/src/routes/api/providers.$providerId.ts +3 -0
  36. package/src/routes/api/providers.ts +2 -0
  37. package/.output/public/assets/ProxyViewerContainer-Cmr2TANl.js +0 -114
  38. package/.output/public/assets/RequestAnatomy-C7BQYj95.js +0 -1
  39. package/.output/public/assets/ResponseView-Bylsp4WJ.js +0 -1
  40. package/.output/public/assets/StreamingChunkSequence-CJH13VD3.js +0 -1
  41. package/.output/public/assets/_sessionId-3e8W68GL.js +0 -1
  42. package/.output/public/assets/index-Bo1dGS4R.css +0 -1
  43. package/.output/public/assets/index-Cz5aqmzE.js +0 -1
@@ -1,11 +1,16 @@
1
1
  import { Info } from "lucide-react";
2
2
  import { type JSX, useMemo, useState } from "react";
3
3
  import { cn, formatTokens } from "../../../lib/utils";
4
+ import { useProviders } from "../../../lib/useProviders";
4
5
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
5
6
  import {
6
7
  analyzeContextIntelligence,
7
8
  CONTEXT_USAGE_THRESHOLDS,
8
9
  type ContextDiagnostic,
10
+ type DuplicateContextGroup,
11
+ type DuplicateContextSegment,
12
+ type ContextHealth,
13
+ type ContextHealthLevel,
9
14
  type ContextIntelligence,
10
15
  type ContextRiskLevel,
11
16
  } from "./contextIntelligence";
@@ -29,6 +34,8 @@ type ContextViewMode = "role" | "segment";
29
34
 
30
35
  const DIVERGENCE_AMBER_THRESHOLD = 0.25;
31
36
  const TOP_CONTRIBUTOR_COUNT = 5;
37
+ const DUPLICATE_GROUP_DISPLAY_COUNT = 4;
38
+ const DUPLICATE_SEGMENT_DISPLAY_COUNT = 6;
32
39
  const ROLE_ORDER: AnatomyRole[] = ["system", "user", "assistant", "tool", "tools"];
33
40
 
34
41
  const ROLE_DESCRIPTIONS: Record<AnatomyRole, string> = {
@@ -58,6 +65,18 @@ const RISK_TEXT_CLASS: Record<ContextRiskLevel, string> = {
58
65
  danger: "text-red-300",
59
66
  };
60
67
 
68
+ const HEALTH_BADGE_CLASS: Record<ContextHealthLevel, string> = {
69
+ ok: "border-emerald-400/30 bg-emerald-400/8 text-emerald-200",
70
+ optimizable: "border-amber-400/30 bg-amber-400/8 text-amber-100",
71
+ risk: "border-red-400/35 bg-red-500/10 text-red-100",
72
+ };
73
+
74
+ const HEALTH_LABEL: Record<ContextHealthLevel, string> = {
75
+ ok: "OK",
76
+ optimizable: "Optimizable",
77
+ risk: "Risk",
78
+ };
79
+
61
80
  function formatPercent(value: number): string {
62
81
  if (value >= 10) return `${value.toFixed(0)}%`;
63
82
  if (value >= 1) return `${value.toFixed(1)}%`;
@@ -85,6 +104,8 @@ function formatContextSource(intelligence: ContextIntelligence): string {
85
104
  switch (intelligence.contextWindow.source) {
86
105
  case "request":
87
106
  return intelligence.contextWindow.label;
107
+ case "provider":
108
+ return intelligence.contextWindow.label;
88
109
  case "model-rule":
89
110
  return intelligence.contextWindow.label;
90
111
  case "unknown":
@@ -103,6 +124,33 @@ function diagnosticTone(diagnostic: ContextDiagnostic): string {
103
124
  }
104
125
  }
105
126
 
127
+ function formatContextHealth(health: ContextHealth): string {
128
+ const label = HEALTH_LABEL[health.level];
129
+ if (health.level === "ok") return label;
130
+ const countLabel =
131
+ health.level === "risk"
132
+ ? `${String(health.opportunityCount)} signal${health.opportunityCount === 1 ? "" : "s"}`
133
+ : `${String(health.opportunityCount)} opportunit${
134
+ health.opportunityCount === 1 ? "y" : "ies"
135
+ }`;
136
+ if (health.reclaimableTokens <= 0) return `${label} | ${countLabel}`;
137
+ return `${label} | ${countLabel} | ~${formatTokens(health.reclaimableTokens)} tokens`;
138
+ }
139
+
140
+ function ContextHealthBadge({ health }: { health: ContextHealth }): JSX.Element {
141
+ return (
142
+ <span
143
+ className={cn(
144
+ "inline-flex h-5 max-w-full items-center rounded border px-1.5 font-mono text-[10px]",
145
+ HEALTH_BADGE_CLASS[health.level],
146
+ )}
147
+ aria-label={`Context health ${formatContextHealth(health)}`}
148
+ >
149
+ <span className="truncate">{formatContextHealth(health)}</span>
150
+ </span>
151
+ );
152
+ }
153
+
106
154
  function ContextIntelligenceStrip({
107
155
  intelligence,
108
156
  }: {
@@ -115,6 +163,10 @@ function ContextIntelligenceStrip({
115
163
  ? "No output reserve"
116
164
  : `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
117
165
  const largestRole = intelligence.largestRole;
166
+ const visibleDiagnostics =
167
+ intelligence.duplicateGroups.length === 0
168
+ ? intelligence.diagnostics
169
+ : intelligence.diagnostics.filter((diagnostic) => diagnostic.kind !== "duplicate");
118
170
 
119
171
  return (
120
172
  <div className="space-y-2">
@@ -199,9 +251,9 @@ function ContextIntelligenceStrip({
199
251
  </div>
200
252
  )}
201
253
 
202
- {intelligence.diagnostics.length > 0 && (
254
+ {visibleDiagnostics.length > 0 && (
203
255
  <div className="grid gap-1.5 sm:grid-cols-2">
204
- {intelligence.diagnostics.map((diagnostic) => (
256
+ {visibleDiagnostics.map((diagnostic) => (
205
257
  <div
206
258
  key={`${diagnostic.kind}-${diagnostic.title}`}
207
259
  className={cn("rounded border px-2 py-1.5 text-xs", diagnosticTone(diagnostic))}
@@ -216,6 +268,116 @@ function ContextIntelligenceStrip({
216
268
  );
217
269
  }
218
270
 
271
+ function resolveDuplicateSegment(
272
+ duplicateSegment: DuplicateContextSegment,
273
+ segments: readonly AnatomySegment[],
274
+ ): AnatomySegment | null {
275
+ return segments.find((segment) => segment.path === duplicateSegment.path) ?? null;
276
+ }
277
+
278
+ function DuplicateContentPanel({
279
+ groups,
280
+ segments,
281
+ onSegmentActivate,
282
+ }: {
283
+ groups: readonly DuplicateContextGroup[];
284
+ segments: readonly AnatomySegment[];
285
+ onSegmentActivate?: (segment: AnatomySegment) => void;
286
+ }): JSX.Element | null {
287
+ if (groups.length === 0) return null;
288
+
289
+ const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
290
+ const hiddenGroupCount = groups.length - visibleGroups.length;
291
+ const totalRepeatedTokens = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
292
+
293
+ return (
294
+ <section className="space-y-2 rounded border border-border/70 bg-muted/15 p-2">
295
+ <div className="flex flex-wrap items-center justify-between gap-2">
296
+ <div className="text-xs font-medium text-foreground">Duplicate Content</div>
297
+ <div className="font-mono text-[11px] text-muted-foreground">
298
+ {String(groups.length)} group{groups.length === 1 ? "" : "s"} | ~
299
+ {formatTokens(totalRepeatedTokens)} repeated
300
+ </div>
301
+ </div>
302
+
303
+ <div className="space-y-2">
304
+ {visibleGroups.map((group, groupIndex) => {
305
+ const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
306
+ const hiddenSegmentCount = group.segments.length - visibleSegments.length;
307
+ return (
308
+ <div
309
+ key={group.key}
310
+ className="space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0"
311
+ >
312
+ <div className="flex min-w-0 items-center gap-2 text-xs">
313
+ <span className="w-5 shrink-0 text-right font-mono text-muted-foreground/70">
314
+ {String(groupIndex + 1)}
315
+ </span>
316
+ <span className="min-w-0 flex-1 truncate text-foreground">{group.firstLabel}</span>
317
+ <span className="shrink-0 font-mono text-muted-foreground">
318
+ x{String(group.count)} | ~{formatTokens(group.repeatedTokens)} saved
319
+ </span>
320
+ </div>
321
+
322
+ {group.preview.length > 0 && (
323
+ <div className="ml-7 line-clamp-2 break-words rounded bg-background/40 px-2 py-1 font-mono text-[11px] leading-4 text-muted-foreground">
324
+ {group.preview}
325
+ </div>
326
+ )}
327
+
328
+ <div className="ml-7 flex flex-wrap gap-1">
329
+ {visibleSegments.map((duplicateSegment, segmentIndex) => {
330
+ const target = resolveDuplicateSegment(duplicateSegment, segments);
331
+ const activate =
332
+ target === null || onSegmentActivate === undefined
333
+ ? undefined
334
+ : onSegmentActivate;
335
+ const label = `${duplicateSegment.label} ~${formatTokens(
336
+ duplicateSegment.tokens,
337
+ )}`;
338
+ if (activate === undefined || target === null) {
339
+ return (
340
+ <span
341
+ key={`${duplicateSegment.path}-${segmentIndex}`}
342
+ className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground"
343
+ title={label}
344
+ >
345
+ <span className="truncate">{label}</span>
346
+ </span>
347
+ );
348
+ }
349
+ return (
350
+ <button
351
+ key={`${duplicateSegment.path}-${segmentIndex}`}
352
+ type="button"
353
+ onClick={() => activate(target)}
354
+ className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground hover:border-border hover:bg-muted/40 hover:text-foreground"
355
+ title="Jump to this duplicate block"
356
+ >
357
+ <span className="truncate">{label}</span>
358
+ </button>
359
+ );
360
+ })}
361
+ {hiddenSegmentCount > 0 && (
362
+ <span className="inline-flex h-6 items-center rounded border border-border/40 px-1.5 font-mono text-[11px] text-muted-foreground/70">
363
+ +{String(hiddenSegmentCount)}
364
+ </span>
365
+ )}
366
+ </div>
367
+ </div>
368
+ );
369
+ })}
370
+ </div>
371
+
372
+ {hiddenGroupCount > 0 && (
373
+ <div className="border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground">
374
+ +{String(hiddenGroupCount)} more duplicate group{hiddenGroupCount === 1 ? "" : "s"}
375
+ </div>
376
+ )}
377
+ </section>
378
+ );
379
+ }
380
+
219
381
  function aggregateByRole(segments: AnatomySegment[]): AnatomySegment[] {
220
382
  const result: AnatomySegment[] = [];
221
383
  for (const role of ROLE_ORDER) {
@@ -254,6 +416,7 @@ export function RequestAnatomy({
254
416
  onSegmentActivate,
255
417
  segments: providedSegments,
256
418
  }: RequestAnatomyProps): JSX.Element | null {
419
+ const { providers } = useProviders();
257
420
  const [viewMode, setViewMode] = useState<ContextViewMode>("role");
258
421
  const segments = useMemo(() => providedSegments ?? null, [providedSegments]);
259
422
  const total = useMemo(
@@ -277,8 +440,9 @@ export function RequestAnatomy({
277
440
  inputTokens,
278
441
  parsed,
279
442
  model,
443
+ providers,
280
444
  }),
281
- [inputTokens, model, parsed, segments],
445
+ [inputTokens, model, parsed, providers, segments],
282
446
  );
283
447
 
284
448
  const showDivergenceWarning = useMemo(() => {
@@ -314,7 +478,10 @@ export function RequestAnatomy({
314
478
  <div className="px-4 py-3 space-y-3" data-testid="anatomy-root">
315
479
  <div className="flex flex-wrap items-start justify-between gap-3">
316
480
  <div className="min-w-0">
317
- <div className="text-sm font-semibold text-foreground">Request Context</div>
481
+ <div className="flex min-w-0 flex-wrap items-center gap-2">
482
+ <div className="text-sm font-semibold text-foreground">Request Context</div>
483
+ {intelligence !== null && <ContextHealthBadge health={intelligence.health} />}
484
+ </div>
318
485
  <div className={cn("mt-0.5 text-xs font-mono tabular-nums", summaryColorClass)}>
319
486
  Estimated ~{formatTokens(total)} tokens | {providerInputLabel} | {contextLimitLabel} |{" "}
320
487
  {usageLabel} | {segmentCountLabel}
@@ -368,6 +535,14 @@ export function RequestAnatomy({
368
535
 
369
536
  {intelligence !== null && <ContextIntelligenceStrip intelligence={intelligence} />}
370
537
 
538
+ {intelligence !== null && (
539
+ <DuplicateContentPanel
540
+ groups={intelligence.duplicateGroups}
541
+ segments={segments}
542
+ onSegmentActivate={onSegmentActivate}
543
+ />
544
+ )}
545
+
371
546
  <SegmentBar
372
547
  segments={displayedSegments}
373
548
  totalTokens={displayedTotal}