@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,6 +1,6 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { f as formatTokens, k as TooltipProvider, c as cn, l as Tooltip, m as TooltipTrigger, n as TooltipContent } from "./ProxyViewerContainer-xesEFppM.mjs";
3
- import { s as safeGetOwnProperty } from "./router-BvuK0vQv.mjs";
2
+ import { u as useProviders, f as formatTokens, k as TooltipProvider, c as cn, l as Tooltip, m as TooltipTrigger, n as TooltipContent, p as countCharacters, q as estimateTokens } from "./ProxyViewerContainer-BiBdKWtj.mjs";
3
+ import { s as safeGetOwnProperty, k as resolveProviderContextWindow, l as isPlainRecord } from "./router-C7S8FRth.mjs";
4
4
  import "../_libs/jszip.mjs";
5
5
  import "../_libs/modelcontextprotocol__server.mjs";
6
6
  import { aa as Info } from "../_libs/lucide-react.mjs";
@@ -171,11 +171,30 @@ const LONG_TOOL_SCHEMA_TOKENS = 8e3;
171
171
  const LARGE_ROLE_PERCENT = 0.35;
172
172
  const DUPLICATE_MIN_TOKENS = 40;
173
173
  const DUPLICATE_MIN_CHARS = 160;
174
+ const DUPLICATE_NESTED_MAX_DEPTH = 5;
175
+ const DUPLICATE_NESTED_MAX_CANDIDATES_PER_SEGMENT = 8;
174
176
  const HISTORY_BLOAT_SEGMENTS = 18;
175
177
  const HISTORY_BLOAT_TOKENS = 6e4;
176
178
  const HISTORY_BLOAT_PERCENT = 0.7;
177
179
  const SYSTEM_HEAVY_TOKENS = 3e3;
178
180
  const SYSTEM_HEAVY_PERCENT = 0.3;
181
+ const DUPLICATE_PREVIEW_CHARS = 160;
182
+ const DUPLICATE_NESTED_FIELD_NAMES = /* @__PURE__ */ new Set([
183
+ "answer",
184
+ "body",
185
+ "content",
186
+ "data",
187
+ "error",
188
+ "message",
189
+ "markdown",
190
+ "output",
191
+ "response",
192
+ "result",
193
+ "stderr",
194
+ "stdout",
195
+ "text",
196
+ "transcript"
197
+ ]);
179
198
  const EXPLICIT_CONTEXT_WINDOW_FIELDS = [
180
199
  "context_window",
181
200
  "contextWindow",
@@ -219,6 +238,12 @@ const MODEL_CONTEXT_RULES = [
219
238
  mode: "prefix",
220
239
  patterns: ["claude-"]
221
240
  },
241
+ {
242
+ family: "DeepSeek V4",
243
+ tokens: 1e6,
244
+ mode: "prefix",
245
+ patterns: ["deepseek-v4-pro", "deepseek-v4-flash"]
246
+ },
222
247
  {
223
248
  family: "DeepSeek",
224
249
  tokens: 64e3,
@@ -226,10 +251,16 @@ const MODEL_CONTEXT_RULES = [
226
251
  patterns: ["deepseek-"]
227
252
  },
228
253
  {
229
- family: "MiniMax",
254
+ family: "MiniMax M3",
230
255
  tokens: 1e6,
231
256
  mode: "includes",
232
- patterns: ["minimax"]
257
+ patterns: ["minimax-m3"]
258
+ },
259
+ {
260
+ family: "MiniMax M2",
261
+ tokens: 204800,
262
+ mode: "includes",
263
+ patterns: ["minimax-m2.7", "minimax-m2.5", "minimax-m2.1"]
233
264
  }
234
265
  ];
235
266
  const ROLE_LABELS = {
@@ -271,7 +302,7 @@ function matchesRule(model, rule) {
271
302
  return rule.patterns.some((pattern) => model.includes(pattern));
272
303
  }
273
304
  }
274
- function resolveContextWindow(model, parsed) {
305
+ function resolveContextWindow(model, parsed, providers = []) {
275
306
  const explicitWindow = readPositiveIntegerField(parsed, EXPLICIT_CONTEXT_WINDOW_FIELDS);
276
307
  if (explicitWindow !== null) {
277
308
  return {
@@ -280,6 +311,14 @@ function resolveContextWindow(model, parsed) {
280
311
  source: "request"
281
312
  };
282
313
  }
314
+ const providerWindow = resolveProviderContextWindow(model, providers);
315
+ if (providerWindow !== null) {
316
+ return {
317
+ tokens: providerWindow.tokens,
318
+ label: `${providerWindow.providerName} config`,
319
+ source: "provider"
320
+ };
321
+ }
283
322
  if (model === null) {
284
323
  return {
285
324
  tokens: null,
@@ -287,12 +326,12 @@ function resolveContextWindow(model, parsed) {
287
326
  source: "unknown"
288
327
  };
289
328
  }
290
- const normalized = model.trim().toLowerCase();
329
+ const normalized = model.trim().toLowerCase().replace(/\s+/g, "-");
291
330
  for (const rule of MODEL_CONTEXT_RULES) {
292
331
  if (matchesRule(normalized, rule)) {
293
332
  return {
294
333
  tokens: rule.tokens,
295
- label: rule.family,
334
+ label: `${rule.family} local rule`,
296
335
  source: "model-rule"
297
336
  };
298
337
  }
@@ -332,42 +371,187 @@ function buildRoleUsages(segments, total) {
332
371
  function normalizeDuplicateText(text) {
333
372
  return text.replace(/\s+/g, " ").trim().toLowerCase();
334
373
  }
335
- function duplicateDiagnostic(segments) {
374
+ function duplicatePreview(text) {
375
+ const singleLine = text.replace(/\s+/g, " ").trim();
376
+ if (singleLine.length <= DUPLICATE_PREVIEW_CHARS) return singleLine;
377
+ return `${singleLine.slice(0, DUPLICATE_PREVIEW_CHARS - 3)}...`;
378
+ }
379
+ function duplicateSegmentRef(candidate) {
380
+ return {
381
+ role: candidate.role,
382
+ label: candidate.label,
383
+ path: candidate.path,
384
+ tokens: candidate.tokens,
385
+ characters: candidate.characters
386
+ };
387
+ }
388
+ function duplicateCandidateFromSegment(segment) {
389
+ return {
390
+ role: segment.role,
391
+ label: segment.label,
392
+ path: segment.path,
393
+ tokens: segment.size,
394
+ characters: segment.characters,
395
+ text: segment.text
396
+ };
397
+ }
398
+ function jsonLikeText(text) {
399
+ const trimmed = text.trim();
400
+ return trimmed.startsWith("{") || trimmed.startsWith("[");
401
+ }
402
+ function parseJsonLikeText(text) {
403
+ if (!jsonLikeText(text)) return null;
404
+ try {
405
+ const parsed = JSON.parse(text);
406
+ return parsed;
407
+ } catch {
408
+ return null;
409
+ }
410
+ }
411
+ function normalizePathFieldName(field) {
412
+ return field.toLowerCase().replace(/[^a-z0-9]/g, "");
413
+ }
414
+ function lastNamedPathField(path) {
415
+ for (let index = path.length - 1; index >= 0; index -= 1) {
416
+ const field = path[index] ?? "";
417
+ if (field.startsWith("[") && field.endsWith("]")) continue;
418
+ return field;
419
+ }
420
+ return null;
421
+ }
422
+ function nestedDuplicateFieldPath(path) {
423
+ const field = lastNamedPathField(path);
424
+ if (field === null) return false;
425
+ return DUPLICATE_NESTED_FIELD_NAMES.has(normalizePathFieldName(field));
426
+ }
427
+ function formatNestedDuplicatePath(path) {
428
+ return path.map((part) => part.startsWith("[") ? part : `.${part}`).join("").replace(/^\./, "");
429
+ }
430
+ function duplicateCandidateFromNestedText(segment, text, path) {
431
+ const characters = countCharacters(text);
432
+ if (characters < DUPLICATE_MIN_CHARS) return null;
433
+ const tokens = estimateTokens(text);
434
+ if (tokens < DUPLICATE_MIN_TOKENS) return null;
435
+ return {
436
+ role: segment.role,
437
+ label: `${segment.label} ${formatNestedDuplicatePath(path)}`,
438
+ path: segment.path,
439
+ tokens,
440
+ characters,
441
+ text
442
+ };
443
+ }
444
+ function collectNestedDuplicateCandidates({
445
+ value,
446
+ segment,
447
+ path,
448
+ depth,
449
+ candidates
450
+ }) {
451
+ if (candidates.length >= DUPLICATE_NESTED_MAX_CANDIDATES_PER_SEGMENT) return;
452
+ if (depth > DUPLICATE_NESTED_MAX_DEPTH) return;
453
+ if (typeof value === "string") {
454
+ if (!nestedDuplicateFieldPath(path)) return;
455
+ const candidate = duplicateCandidateFromNestedText(segment, value, path);
456
+ if (candidate !== null) candidates.push(candidate);
457
+ return;
458
+ }
459
+ if (Array.isArray(value)) {
460
+ value.forEach(
461
+ (item, index) => collectNestedDuplicateCandidates({
462
+ value: item,
463
+ segment,
464
+ path: [...path, `[${String(index)}]`],
465
+ depth: depth + 1,
466
+ candidates
467
+ })
468
+ );
469
+ return;
470
+ }
471
+ if (!isPlainRecord(value)) return;
472
+ Object.entries(value).forEach(
473
+ ([key, child]) => collectNestedDuplicateCandidates({
474
+ value: child,
475
+ segment,
476
+ path: [...path, key],
477
+ depth: depth + 1,
478
+ candidates
479
+ })
480
+ );
481
+ }
482
+ function duplicateCandidatesForSegment(segment) {
483
+ const segmentCandidate = duplicateCandidateFromSegment(segment);
484
+ const parsed = parseJsonLikeText(segment.text);
485
+ if (parsed === null) return [segmentCandidate];
486
+ const candidates = [];
487
+ collectNestedDuplicateCandidates({
488
+ value: parsed,
489
+ segment,
490
+ path: [],
491
+ depth: 0,
492
+ candidates
493
+ });
494
+ const normalizedSegment = normalizeDuplicateText(segment.text);
495
+ const seen = /* @__PURE__ */ new Set([normalizedSegment]);
496
+ const uniqueNested = candidates.filter((candidate) => {
497
+ const normalized = normalizeDuplicateText(candidate.text);
498
+ if (normalized === "") return false;
499
+ if (seen.has(normalized)) return false;
500
+ seen.add(normalized);
501
+ return true;
502
+ });
503
+ return [segmentCandidate, ...uniqueNested];
504
+ }
505
+ function duplicateContextGroups(segments) {
336
506
  const buckets = /* @__PURE__ */ new Map();
337
507
  for (const segment of segments) {
338
- if (segment.size < DUPLICATE_MIN_TOKENS) continue;
339
- if (segment.characters < DUPLICATE_MIN_CHARS) continue;
340
- const normalized = normalizeDuplicateText(segment.text);
341
- if (normalized === "") continue;
342
- const existing = buckets.get(normalized);
343
- if (existing === void 0) {
508
+ const candidates = duplicateCandidatesForSegment(segment);
509
+ for (const candidate of candidates) {
510
+ if (candidate.tokens < DUPLICATE_MIN_TOKENS) continue;
511
+ if (candidate.characters < DUPLICATE_MIN_CHARS) continue;
512
+ const normalized = normalizeDuplicateText(candidate.text);
513
+ if (normalized === "") continue;
514
+ const existing = buckets.get(normalized);
515
+ if (existing === void 0) {
516
+ buckets.set(normalized, {
517
+ tokens: candidate.tokens,
518
+ candidates: [candidate]
519
+ });
520
+ continue;
521
+ }
344
522
  buckets.set(normalized, {
345
- count: 1,
346
- tokens: segment.size,
347
- firstTokens: segment.size,
348
- label: segment.label
523
+ tokens: existing.tokens + candidate.tokens,
524
+ candidates: [...existing.candidates, candidate]
349
525
  });
350
- continue;
351
526
  }
352
- buckets.set(normalized, {
353
- count: existing.count + 1,
354
- tokens: existing.tokens + segment.size,
355
- firstTokens: existing.firstTokens,
356
- label: existing.label
357
- });
358
527
  }
359
- let repeatedGroups = 0;
360
- let repeatedSegments = 0;
361
- let repeatedTokens = 0;
362
- let largest = null;
528
+ const groups = [];
363
529
  for (const bucket of buckets.values()) {
364
- if (bucket.count < 2) continue;
365
- repeatedGroups += 1;
366
- repeatedSegments += bucket.count;
367
- repeatedTokens += bucket.tokens - bucket.firstTokens;
368
- if (largest === null || bucket.tokens > largest.tokens) largest = bucket;
530
+ if (bucket.candidates.length < 2) continue;
531
+ const firstCandidate = bucket.candidates[0] ?? null;
532
+ if (firstCandidate === null) continue;
533
+ const repeatedTokens = bucket.tokens - firstCandidate.tokens;
534
+ groups.push({
535
+ key: `${firstCandidate.path}:${firstCandidate.label}:${String(
536
+ bucket.candidates.length
537
+ )}:${String(bucket.tokens)}`,
538
+ count: bucket.candidates.length,
539
+ totalTokens: bucket.tokens,
540
+ repeatedTokens,
541
+ preview: duplicatePreview(firstCandidate.text),
542
+ firstLabel: firstCandidate.label,
543
+ segments: bucket.candidates.map(duplicateSegmentRef)
544
+ });
369
545
  }
370
- if (repeatedGroups === 0 || largest === null) return null;
546
+ return groups.sort(
547
+ (left, right) => right.repeatedTokens - left.repeatedTokens || right.totalTokens - left.totalTokens
548
+ );
549
+ }
550
+ function duplicateDiagnostic(duplicateGroups) {
551
+ if (duplicateGroups.length === 0) return null;
552
+ const repeatedGroups = duplicateGroups.length;
553
+ const repeatedSegments = duplicateGroups.reduce((sum, group) => sum + group.count, 0);
554
+ const repeatedTokens = duplicateGroups.reduce((sum, group) => sum + group.repeatedTokens, 0);
371
555
  return {
372
556
  kind: "duplicate",
373
557
  severity: repeatedTokens >= LONG_TOOL_SCHEMA_TOKENS ? "danger" : "watch",
@@ -377,6 +561,9 @@ function duplicateDiagnostic(segments) {
377
561
  )} group${repeatedGroups === 1 ? "" : "s"}, ~${String(repeatedTokens)} repeated tokens.`
378
562
  };
379
563
  }
564
+ function duplicateReclaimableTokens(duplicateGroups) {
565
+ return duplicateGroups.reduce((sum, group) => sum + group.repeatedTokens, 0);
566
+ }
380
567
  function diagnosticsForRolePressure(roleUsages, total, messageSegmentCount) {
381
568
  const diagnostics = [];
382
569
  const toolUsage = roleUsages.find((usage) => usage.role === "tools") ?? null;
@@ -411,17 +598,36 @@ function diagnosticsForRolePressure(roleUsages, total, messageSegmentCount) {
411
598
  }
412
599
  return diagnostics;
413
600
  }
601
+ function contextHealth({
602
+ risk,
603
+ contextWindow,
604
+ diagnostics,
605
+ duplicateGroups
606
+ }) {
607
+ const diagnosticKinds = new Set(diagnostics.map((diagnostic) => diagnostic.kind));
608
+ let opportunityCount = diagnosticKinds.size;
609
+ if (risk === "watch" || risk === "danger") opportunityCount += 1;
610
+ if (contextWindow.source === "unknown") opportunityCount += 1;
611
+ const hasDangerDiagnostic = diagnostics.some((diagnostic) => diagnostic.severity === "danger");
612
+ const level = risk === "danger" || hasDangerDiagnostic ? "risk" : opportunityCount > 0 ? "optimizable" : "ok";
613
+ return {
614
+ level,
615
+ opportunityCount,
616
+ reclaimableTokens: duplicateReclaimableTokens(duplicateGroups)
617
+ };
618
+ }
414
619
  function analyzeContextIntelligence({
415
620
  segments,
416
621
  inputTokens,
417
622
  parsed,
418
- model
623
+ model,
624
+ providers = []
419
625
  }) {
420
626
  const totalEstimatedTokens = segments.reduce((sum, segment) => sum + segment.size, 0);
421
627
  const requestModel = model ?? readRequestModel(parsed);
422
628
  const estimatedInputTokens = inputTokens ?? totalEstimatedTokens;
423
629
  const outputReserveTokens = readPositiveIntegerField(parsed, OUTPUT_RESERVE_FIELDS);
424
- const contextWindow = resolveContextWindow(requestModel, parsed);
630
+ const contextWindow = resolveContextWindow(requestModel, parsed, providers);
425
631
  const reservedTokens = outputReserveTokens ?? 0;
426
632
  const windowUsedTokens = contextWindow.tokens === null ? null : estimatedInputTokens + reservedTokens;
427
633
  const remainingInputTokens = contextWindow.tokens === null ? null : contextWindow.tokens - estimatedInputTokens;
@@ -430,13 +636,15 @@ function analyzeContextIntelligence({
430
636
  const roleUsages = buildRoleUsages(segments, totalEstimatedTokens);
431
637
  const largestRole = roleUsages[0] ?? null;
432
638
  const messageSegmentCount = segments.filter((segment) => segment.role !== "tools").length;
433
- const duplicate = duplicateDiagnostic(segments);
639
+ const duplicateGroups = duplicateContextGroups(segments);
640
+ const duplicate = duplicateDiagnostic(duplicateGroups);
434
641
  const diagnostics = diagnosticsForRolePressure(
435
642
  roleUsages,
436
643
  totalEstimatedTokens,
437
644
  messageSegmentCount
438
645
  );
439
646
  if (duplicate !== null) diagnostics.unshift(duplicate);
647
+ const risk = riskLevel(usagePercent);
440
648
  return {
441
649
  model: requestModel,
442
650
  contextWindow,
@@ -446,10 +654,17 @@ function analyzeContextIntelligence({
446
654
  remainingInputTokens,
447
655
  remainingAfterReserveTokens,
448
656
  usagePercent,
449
- riskLevel: riskLevel(usagePercent),
657
+ riskLevel: risk,
450
658
  roleUsages,
451
659
  largestRole,
452
- diagnostics
660
+ diagnostics,
661
+ duplicateGroups,
662
+ health: contextHealth({
663
+ risk,
664
+ contextWindow,
665
+ diagnostics,
666
+ duplicateGroups
667
+ })
453
668
  };
454
669
  }
455
670
  const CONTEXT_USAGE_THRESHOLDS = {
@@ -662,6 +877,8 @@ const SegmentBar = reactExports.memo(function SegmentBar2({
662
877
  });
663
878
  const DIVERGENCE_AMBER_THRESHOLD = 0.25;
664
879
  const TOP_CONTRIBUTOR_COUNT = 5;
880
+ const DUPLICATE_GROUP_DISPLAY_COUNT = 4;
881
+ const DUPLICATE_SEGMENT_DISPLAY_COUNT = 6;
665
882
  const ROLE_ORDER = ["system", "user", "assistant", "tool", "tools"];
666
883
  const ROLE_DESCRIPTIONS = {
667
884
  system: "instructions",
@@ -686,6 +903,16 @@ const RISK_TEXT_CLASS = {
686
903
  watch: "text-amber-300",
687
904
  danger: "text-red-300"
688
905
  };
906
+ const HEALTH_BADGE_CLASS = {
907
+ ok: "border-emerald-400/30 bg-emerald-400/8 text-emerald-200",
908
+ optimizable: "border-amber-400/30 bg-amber-400/8 text-amber-100",
909
+ risk: "border-red-400/35 bg-red-500/10 text-red-100"
910
+ };
911
+ const HEALTH_LABEL = {
912
+ ok: "OK",
913
+ optimizable: "Optimizable",
914
+ risk: "Risk"
915
+ };
689
916
  function formatPercent(value) {
690
917
  if (value >= 10) return `${value.toFixed(0)}%`;
691
918
  if (value >= 1) return `${value.toFixed(1)}%`;
@@ -709,6 +936,8 @@ function formatContextSource(intelligence) {
709
936
  switch (intelligence.contextWindow.source) {
710
937
  case "request":
711
938
  return intelligence.contextWindow.label;
939
+ case "provider":
940
+ return intelligence.contextWindow.label;
712
941
  case "model-rule":
713
942
  return intelligence.contextWindow.label;
714
943
  case "unknown":
@@ -723,12 +952,33 @@ function diagnosticTone(diagnostic) {
723
952
  return "border-red-400/35 bg-red-500/10 text-red-100";
724
953
  }
725
954
  }
955
+ function formatContextHealth(health) {
956
+ const label = HEALTH_LABEL[health.level];
957
+ if (health.level === "ok") return label;
958
+ const countLabel = health.level === "risk" ? `${String(health.opportunityCount)} signal${health.opportunityCount === 1 ? "" : "s"}` : `${String(health.opportunityCount)} opportunit${health.opportunityCount === 1 ? "y" : "ies"}`;
959
+ if (health.reclaimableTokens <= 0) return `${label} | ${countLabel}`;
960
+ return `${label} | ${countLabel} | ~${formatTokens(health.reclaimableTokens)} tokens`;
961
+ }
962
+ function ContextHealthBadge({ health }) {
963
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
964
+ "span",
965
+ {
966
+ className: cn(
967
+ "inline-flex h-5 max-w-full items-center rounded border px-1.5 font-mono text-[10px]",
968
+ HEALTH_BADGE_CLASS[health.level]
969
+ ),
970
+ "aria-label": `Context health ${formatContextHealth(health)}`,
971
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: formatContextHealth(health) })
972
+ }
973
+ );
974
+ }
726
975
  function ContextIntelligenceStrip({
727
976
  intelligence
728
977
  }) {
729
978
  const usageWidth = intelligence.usagePercent === null ? 0 : Math.max(1, intelligence.usagePercent * 100);
730
979
  const outputReserveLabel = intelligence.outputReserveTokens === null ? "No output reserve" : `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
731
980
  const largestRole = intelligence.largestRole;
981
+ const visibleDiagnostics = intelligence.duplicateGroups.length === 0 ? intelligence.diagnostics : intelligence.diagnostics.filter((diagnostic) => diagnostic.kind !== "duplicate");
732
982
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
733
983
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-x-4 gap-y-2 border-y border-border/60 py-2 text-xs sm:grid-cols-4", children: [
734
984
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
@@ -810,7 +1060,7 @@ function ContextIntelligenceStrip({
810
1060
  ] })
811
1061
  ] })
812
1062
  ] }),
813
- intelligence.diagnostics.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-1.5 sm:grid-cols-2", children: intelligence.diagnostics.map((diagnostic) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1063
+ visibleDiagnostics.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-1.5 sm:grid-cols-2", children: visibleDiagnostics.map((diagnostic) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
814
1064
  "div",
815
1065
  {
816
1066
  className: cn("rounded border px-2 py-1.5 text-xs", diagnosticTone(diagnostic)),
@@ -823,6 +1073,98 @@ function ContextIntelligenceStrip({
823
1073
  )) })
824
1074
  ] });
825
1075
  }
1076
+ function resolveDuplicateSegment(duplicateSegment, segments) {
1077
+ return segments.find((segment) => segment.path === duplicateSegment.path) ?? null;
1078
+ }
1079
+ function DuplicateContentPanel({
1080
+ groups,
1081
+ segments,
1082
+ onSegmentActivate
1083
+ }) {
1084
+ if (groups.length === 0) return null;
1085
+ const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
1086
+ const hiddenGroupCount = groups.length - visibleGroups.length;
1087
+ const totalRepeatedTokens = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
1088
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "space-y-2 rounded border border-border/70 bg-muted/15 p-2", children: [
1089
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1090
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-medium text-foreground", children: "Duplicate Content" }),
1091
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono text-[11px] text-muted-foreground", children: [
1092
+ String(groups.length),
1093
+ " group",
1094
+ groups.length === 1 ? "" : "s",
1095
+ " | ~",
1096
+ formatTokens(totalRepeatedTokens),
1097
+ " repeated"
1098
+ ] })
1099
+ ] }),
1100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: visibleGroups.map((group, groupIndex) => {
1101
+ const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
1102
+ const hiddenSegmentCount = group.segments.length - visibleSegments.length;
1103
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1104
+ "div",
1105
+ {
1106
+ className: "space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0",
1107
+ children: [
1108
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2 text-xs", children: [
1109
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-5 shrink-0 text-right font-mono text-muted-foreground/70", children: String(groupIndex + 1) }),
1110
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-foreground", children: group.firstLabel }),
1111
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 font-mono text-muted-foreground", children: [
1112
+ "x",
1113
+ String(group.count),
1114
+ " | ~",
1115
+ formatTokens(group.repeatedTokens),
1116
+ " saved"
1117
+ ] })
1118
+ ] }),
1119
+ group.preview.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("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", children: group.preview }),
1120
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-7 flex flex-wrap gap-1", children: [
1121
+ visibleSegments.map((duplicateSegment, segmentIndex) => {
1122
+ const target = resolveDuplicateSegment(duplicateSegment, segments);
1123
+ const activate = target === null || onSegmentActivate === void 0 ? void 0 : onSegmentActivate;
1124
+ const label = `${duplicateSegment.label} ~${formatTokens(
1125
+ duplicateSegment.tokens
1126
+ )}`;
1127
+ if (activate === void 0 || target === null) {
1128
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1129
+ "span",
1130
+ {
1131
+ className: "inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground",
1132
+ title: label,
1133
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label })
1134
+ },
1135
+ `${duplicateSegment.path}-${segmentIndex}`
1136
+ );
1137
+ }
1138
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1139
+ "button",
1140
+ {
1141
+ type: "button",
1142
+ onClick: () => activate(target),
1143
+ 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",
1144
+ title: "Jump to this duplicate block",
1145
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label })
1146
+ },
1147
+ `${duplicateSegment.path}-${segmentIndex}`
1148
+ );
1149
+ }),
1150
+ hiddenSegmentCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex h-6 items-center rounded border border-border/40 px-1.5 font-mono text-[11px] text-muted-foreground/70", children: [
1151
+ "+",
1152
+ String(hiddenSegmentCount)
1153
+ ] })
1154
+ ] })
1155
+ ]
1156
+ },
1157
+ group.key
1158
+ );
1159
+ }) }),
1160
+ hiddenGroupCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground", children: [
1161
+ "+",
1162
+ String(hiddenGroupCount),
1163
+ " more duplicate group",
1164
+ hiddenGroupCount === 1 ? "" : "s"
1165
+ ] })
1166
+ ] });
1167
+ }
826
1168
  function aggregateByRole(segments) {
827
1169
  const result = [];
828
1170
  for (const role of ROLE_ORDER) {
@@ -853,6 +1195,7 @@ function RequestAnatomy({
853
1195
  onSegmentActivate,
854
1196
  segments: providedSegments
855
1197
  }) {
1198
+ const { providers } = useProviders();
856
1199
  const [viewMode, setViewMode] = reactExports.useState("role");
857
1200
  const segments = reactExports.useMemo(() => providedSegments ?? null, [providedSegments]);
858
1201
  const total = reactExports.useMemo(
@@ -872,9 +1215,10 @@ function RequestAnatomy({
872
1215
  segments,
873
1216
  inputTokens,
874
1217
  parsed,
875
- model
1218
+ model,
1219
+ providers
876
1220
  }),
877
- [inputTokens, model, parsed, segments]
1221
+ [inputTokens, model, parsed, providers, segments]
878
1222
  );
879
1223
  const showDivergenceWarning = reactExports.useMemo(() => {
880
1224
  if (segments === null) return false;
@@ -897,7 +1241,10 @@ function RequestAnatomy({
897
1241
  return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { delayDuration: 150, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 space-y-3", "data-testid": "anatomy-root", children: [
898
1242
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
899
1243
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
900
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Request Context" }),
1244
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
1245
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Request Context" }),
1246
+ intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextHealthBadge, { health: intelligence.health })
1247
+ ] }),
901
1248
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("mt-0.5 text-xs font-mono tabular-nums", summaryColorClass), children: [
902
1249
  "Estimated ~",
903
1250
  formatTokens(total),
@@ -951,6 +1298,14 @@ function RequestAnatomy({
951
1298
  "Estimate differs from provider-reported input."
952
1299
  ] }),
953
1300
  intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextIntelligenceStrip, { intelligence }),
1301
+ intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
1302
+ DuplicateContentPanel,
1303
+ {
1304
+ groups: intelligence.duplicateGroups,
1305
+ segments,
1306
+ onSegmentActivate
1307
+ }
1308
+ ),
954
1309
  /* @__PURE__ */ jsxRuntimeExports.jsx(
955
1310
  SegmentBar,
956
1311
  {
@@ -1,6 +1,6 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { g as getLogFormatAdapter, f as formatTokens, c as cn, p as getStatusCategory, B as Badge, C as Collapsible, q as CollapsibleTrigger, s as CollapsibleContent, S as ScrollArea, t as JsonViewer, v as safeJsonValue } from "./ProxyViewerContainer-xesEFppM.mjs";
3
- import "./router-BvuK0vQv.mjs";
2
+ import { g as getLogFormatAdapter, f as formatTokens, c as cn, s as getStatusCategory, B as Badge, C as Collapsible, t as CollapsibleTrigger, v as CollapsibleContent, S as ScrollArea, w as JsonViewer, x as safeJsonValue } from "./ProxyViewerContainer-BiBdKWtj.mjs";
3
+ import "./router-C7S8FRth.mjs";
4
4
  import "../_libs/modelcontextprotocol__server.mjs";
5
5
  import "../_libs/jszip.mjs";
6
6
  import { Z as Zap, j as TriangleAlert, ab as CircleStop, B as Brain, b as ChevronDown, f as ChevronRight, T as Terminal } from "../_libs/lucide-react.mjs";
@@ -1,6 +1,6 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { k as TooltipProvider, l as Tooltip, m as TooltipTrigger, B as Badge, n as TooltipContent, t as JsonViewer } from "./ProxyViewerContainer-xesEFppM.mjs";
3
- import "./router-BvuK0vQv.mjs";
2
+ import { k as TooltipProvider, l as Tooltip, m as TooltipTrigger, B as Badge, n as TooltipContent, w as JsonViewer } from "./ProxyViewerContainer-BiBdKWtj.mjs";
3
+ import "./router-C7S8FRth.mjs";
4
4
  import "../_libs/modelcontextprotocol__server.mjs";
5
5
  import "../_libs/jszip.mjs";
6
6
  import { b as ChevronDown, f as ChevronRight, L as LoaderCircle } from "../_libs/lucide-react.mjs";
@@ -1,6 +1,6 @@
1
- import { P as ProxyViewerContainer } from "./ProxyViewerContainer-xesEFppM.mjs";
1
+ import { P as ProxyViewerContainer } from "./ProxyViewerContainer-BiBdKWtj.mjs";
2
2
  import "../_libs/react.mjs";
3
- import "./router-BvuK0vQv.mjs";
3
+ import "./router-C7S8FRth.mjs";
4
4
  import "../_libs/modelcontextprotocol__server.mjs";
5
5
  import "../_libs/jszip.mjs";
6
6
  import "../_libs/swr.mjs";
@@ -198,7 +198,7 @@ function getResponse() {
198
198
  return event.res;
199
199
  }
200
200
  async function getStartManifest(matchedRoutes) {
201
- const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-DGw3vVOm.mjs");
201
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-CVTkFOdT.mjs");
202
202
  const startManifest = tsrStartManifest();
203
203
  const rootRoute = startManifest.routes[rootRouteId] = startManifest.routes[rootRouteId] || {};
204
204
  rootRoute.assets = rootRoute.assets || [];
@@ -767,7 +767,7 @@ let entriesPromise;
767
767
  let baseManifestPromise;
768
768
  let cachedFinalManifestPromise;
769
769
  async function loadEntries() {
770
- const routerEntry = await import("./router-BvuK0vQv.mjs").then((n) => n.i);
770
+ const routerEntry = await import("./router-C7S8FRth.mjs").then((n) => n.m);
771
771
  const startEntry = await import("./start-HYkvq4Ni.mjs");
772
772
  return { startEntry, routerEntry };
773
773
  }