@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-Cb4RH049.js → CompareDrawer-C9OWdxHM.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-CNzay4u8.js +114 -0
- package/.output/public/assets/{ReplayDialog-B3y8E7IE.js → ReplayDialog-CwE6I1Pe.js} +1 -1
- package/.output/public/assets/RequestAnatomy-DEehC3yz.js +1 -0
- package/.output/public/assets/ResponseView-CE5UsuUq.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-CLcLZ7-W.js +1 -0
- package/.output/public/assets/_sessionId-DE__tPTo.js +1 -0
- package/.output/public/assets/index-BFO4Jmw5.js +1 -0
- package/.output/public/assets/index-CDzZ7l_S.css +1 -0
- package/.output/public/assets/{main-Bww0Bc88.js → main-D1Xanzu7.js} +2 -2
- package/.output/server/{_sessionId-BnCF8Ito.mjs → _sessionId-D3IyPpws.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-jPU14EI3.mjs → CompareDrawer-BIMThqxy.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-xesEFppM.mjs → ProxyViewerContainer-BiBdKWtj.mjs} +159 -14
- package/.output/server/_ssr/{ReplayDialog-DQvW2zoW.mjs → ReplayDialog-D_wiDyM2.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-BilEphjP.mjs → RequestAnatomy-5UiMTESr.mjs} +399 -44
- package/.output/server/_ssr/{ResponseView-DB5PPcWf.mjs → ResponseView-Drk5ghmL.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Dnrgn2AP.mjs → StreamingChunkSequence-F889rmG0.mjs} +2 -2
- package/.output/server/_ssr/{index-YPp821bH.mjs → index-CaPniq9z.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-BvuK0vQv.mjs → router-C7S8FRth.mjs} +531 -111
- package/.output/server/{_tanstack-start-manifest_v-DGw3vVOm.mjs → _tanstack-start-manifest_v-CVTkFOdT.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/package.json +1 -1
- package/src/components/providers/ProviderCard.tsx +59 -5
- package/src/components/providers/ProviderForm.tsx +42 -0
- package/src/components/providers/ProvidersPanel.tsx +68 -0
- package/src/components/proxy-viewer/LogEntry.tsx +6 -1
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +179 -4
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +346 -41
- package/src/lib/providerContract.ts +12 -0
- package/src/lib/providerModelMetadata.ts +327 -0
- package/src/proxy/providers.ts +112 -52
- package/src/routes/api/providers.$providerId.model-metadata.ts +134 -0
- package/src/routes/api/providers.$providerId.ts +3 -0
- package/src/routes/api/providers.ts +2 -0
- package/.output/public/assets/ProxyViewerContainer-Cmr2TANl.js +0 -114
- package/.output/public/assets/RequestAnatomy-C7BQYj95.js +0 -1
- package/.output/public/assets/ResponseView-Bylsp4WJ.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-CJH13VD3.js +0 -1
- package/.output/public/assets/_sessionId-3e8W68GL.js +0 -1
- package/.output/public/assets/index-Bo1dGS4R.css +0 -1
- 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
|
-
{
|
|
254
|
+
{visibleDiagnostics.length > 0 && (
|
|
203
255
|
<div className="grid gap-1.5 sm:grid-cols-2">
|
|
204
|
-
{
|
|
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="
|
|
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}
|