@tonyclaw/agent-inspector 2.0.8 → 2.0.10

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 (44) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Djb4XFZS.js → CompareDrawer-BWMipEjS.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DIAgurux.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-DBPMIs2I.js → ReplayDialog-DaSzZsvM.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-BfQmPHCd.js +1 -0
  6. package/.output/public/assets/ResponseView-CblEWDMx.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-B4atnL99.js +1 -0
  8. package/.output/public/assets/_sessionId-DUzYnZXV.js +1 -0
  9. package/.output/public/assets/index-CxhRsekH.css +1 -0
  10. package/.output/public/assets/index-jagRsQaV.js +1 -0
  11. package/.output/public/assets/{main-CwhqfJqM.js → main-DE6Y4-wV.js} +2 -2
  12. package/.output/server/{_sessionId-DzBy9gLa.mjs → _sessionId-61HO9rUI.mjs} +3 -3
  13. package/.output/server/_ssr/{CompareDrawer-C5NBvE2e.mjs → CompareDrawer-CSPqneYm.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-DiZjOTQT.mjs → ProxyViewerContainer-DrSMa5O7.mjs} +159 -14
  15. package/.output/server/_ssr/{ReplayDialog-EpvTwFvp.mjs → ReplayDialog-Dn3naENv.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-DaQjhixp.mjs → RequestAnatomy-D1X3f3AX.mjs} +336 -42
  17. package/.output/server/_ssr/{ResponseView-D8tGHCGY.mjs → ResponseView-CwXAWOoE.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-DGQnb1QE.mjs → StreamingChunkSequence-DRSR2FfN.mjs} +2 -2
  19. package/.output/server/_ssr/{index-N25hqa7t.mjs → index-D4zn5CAg.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-Bf0m6F0G.mjs → router-CSRaa_tu.mjs} +531 -111
  22. package/.output/server/{_tanstack-start-manifest_v-DlAyJ5DB.mjs → _tanstack-start-manifest_v-CfvVUYYj.mjs} +1 -1
  23. package/.output/server/index.mjs +60 -60
  24. package/bin/agent-inspector.js +2 -0
  25. package/package.json +3 -2
  26. package/src/components/providers/ProviderCard.tsx +59 -5
  27. package/src/components/providers/ProviderForm.tsx +42 -0
  28. package/src/components/providers/ProvidersPanel.tsx +68 -0
  29. package/src/components/proxy-viewer/LogEntry.tsx +6 -1
  30. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +134 -3
  31. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +298 -40
  32. package/src/lib/providerContract.ts +12 -0
  33. package/src/lib/providerModelMetadata.ts +327 -0
  34. package/src/proxy/providers.ts +112 -52
  35. package/src/routes/api/providers.$providerId.model-metadata.ts +134 -0
  36. package/src/routes/api/providers.$providerId.ts +3 -0
  37. package/src/routes/api/providers.ts +2 -0
  38. package/.output/public/assets/ProxyViewerContainer-CWdkPZsJ.js +0 -114
  39. package/.output/public/assets/RequestAnatomy-FBfRNQN7.js +0 -1
  40. package/.output/public/assets/ResponseView-Db4pJL7U.js +0 -1
  41. package/.output/public/assets/StreamingChunkSequence-BH175O6R.js +0 -1
  42. package/.output/public/assets/_sessionId-3ipVoQtW.js +0 -1
  43. package/.output/public/assets/index-Bo1dGS4R.css +0 -1
  44. package/.output/public/assets/index-CJgaCJo8.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-DiZjOTQT.mjs";
3
- import { s as safeGetOwnProperty } from "./router-Bf0m6F0G.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-DrSMa5O7.mjs";
3
+ import { s as safeGetOwnProperty, k as resolveProviderContextWindow, l as isPlainRecord } from "./router-CSRaa_tu.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",
@@ -415,13 +599,14 @@ function analyzeContextIntelligence({
415
599
  segments,
416
600
  inputTokens,
417
601
  parsed,
418
- model
602
+ model,
603
+ providers = []
419
604
  }) {
420
605
  const totalEstimatedTokens = segments.reduce((sum, segment) => sum + segment.size, 0);
421
606
  const requestModel = model ?? readRequestModel(parsed);
422
607
  const estimatedInputTokens = inputTokens ?? totalEstimatedTokens;
423
608
  const outputReserveTokens = readPositiveIntegerField(parsed, OUTPUT_RESERVE_FIELDS);
424
- const contextWindow = resolveContextWindow(requestModel, parsed);
609
+ const contextWindow = resolveContextWindow(requestModel, parsed, providers);
425
610
  const reservedTokens = outputReserveTokens ?? 0;
426
611
  const windowUsedTokens = contextWindow.tokens === null ? null : estimatedInputTokens + reservedTokens;
427
612
  const remainingInputTokens = contextWindow.tokens === null ? null : contextWindow.tokens - estimatedInputTokens;
@@ -430,7 +615,8 @@ function analyzeContextIntelligence({
430
615
  const roleUsages = buildRoleUsages(segments, totalEstimatedTokens);
431
616
  const largestRole = roleUsages[0] ?? null;
432
617
  const messageSegmentCount = segments.filter((segment) => segment.role !== "tools").length;
433
- const duplicate = duplicateDiagnostic(segments);
618
+ const duplicateGroups = duplicateContextGroups(segments);
619
+ const duplicate = duplicateDiagnostic(duplicateGroups);
434
620
  const diagnostics = diagnosticsForRolePressure(
435
621
  roleUsages,
436
622
  totalEstimatedTokens,
@@ -449,7 +635,8 @@ function analyzeContextIntelligence({
449
635
  riskLevel: riskLevel(usagePercent),
450
636
  roleUsages,
451
637
  largestRole,
452
- diagnostics
638
+ diagnostics,
639
+ duplicateGroups
453
640
  };
454
641
  }
455
642
  const CONTEXT_USAGE_THRESHOLDS = {
@@ -662,6 +849,8 @@ const SegmentBar = reactExports.memo(function SegmentBar2({
662
849
  });
663
850
  const DIVERGENCE_AMBER_THRESHOLD = 0.25;
664
851
  const TOP_CONTRIBUTOR_COUNT = 5;
852
+ const DUPLICATE_GROUP_DISPLAY_COUNT = 4;
853
+ const DUPLICATE_SEGMENT_DISPLAY_COUNT = 6;
665
854
  const ROLE_ORDER = ["system", "user", "assistant", "tool", "tools"];
666
855
  const ROLE_DESCRIPTIONS = {
667
856
  system: "instructions",
@@ -709,6 +898,8 @@ function formatContextSource(intelligence) {
709
898
  switch (intelligence.contextWindow.source) {
710
899
  case "request":
711
900
  return intelligence.contextWindow.label;
901
+ case "provider":
902
+ return intelligence.contextWindow.label;
712
903
  case "model-rule":
713
904
  return intelligence.contextWindow.label;
714
905
  case "unknown":
@@ -729,6 +920,7 @@ function ContextIntelligenceStrip({
729
920
  const usageWidth = intelligence.usagePercent === null ? 0 : Math.max(1, intelligence.usagePercent * 100);
730
921
  const outputReserveLabel = intelligence.outputReserveTokens === null ? "No output reserve" : `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
731
922
  const largestRole = intelligence.largestRole;
923
+ const visibleDiagnostics = intelligence.duplicateGroups.length === 0 ? intelligence.diagnostics : intelligence.diagnostics.filter((diagnostic) => diagnostic.kind !== "duplicate");
732
924
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
733
925
  /* @__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
926
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
@@ -810,7 +1002,7 @@ function ContextIntelligenceStrip({
810
1002
  ] })
811
1003
  ] })
812
1004
  ] }),
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(
1005
+ visibleDiagnostics.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-1.5 sm:grid-cols-2", children: visibleDiagnostics.map((diagnostic) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
814
1006
  "div",
815
1007
  {
816
1008
  className: cn("rounded border px-2 py-1.5 text-xs", diagnosticTone(diagnostic)),
@@ -823,6 +1015,98 @@ function ContextIntelligenceStrip({
823
1015
  )) })
824
1016
  ] });
825
1017
  }
1018
+ function resolveDuplicateSegment(duplicateSegment, segments) {
1019
+ return segments.find((segment) => segment.path === duplicateSegment.path) ?? null;
1020
+ }
1021
+ function DuplicateContentPanel({
1022
+ groups,
1023
+ segments,
1024
+ onSegmentActivate
1025
+ }) {
1026
+ if (groups.length === 0) return null;
1027
+ const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
1028
+ const hiddenGroupCount = groups.length - visibleGroups.length;
1029
+ const totalRepeatedTokens = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
1030
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "space-y-2 rounded border border-border/70 bg-muted/15 p-2", children: [
1031
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1032
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-medium text-foreground", children: "Duplicate Content" }),
1033
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-mono text-[11px] text-muted-foreground", children: [
1034
+ String(groups.length),
1035
+ " group",
1036
+ groups.length === 1 ? "" : "s",
1037
+ " | ~",
1038
+ formatTokens(totalRepeatedTokens),
1039
+ " repeated"
1040
+ ] })
1041
+ ] }),
1042
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: visibleGroups.map((group, groupIndex) => {
1043
+ const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
1044
+ const hiddenSegmentCount = group.segments.length - visibleSegments.length;
1045
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1046
+ "div",
1047
+ {
1048
+ className: "space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0",
1049
+ children: [
1050
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2 text-xs", children: [
1051
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-5 shrink-0 text-right font-mono text-muted-foreground/70", children: String(groupIndex + 1) }),
1052
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-foreground", children: group.firstLabel }),
1053
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 font-mono text-muted-foreground", children: [
1054
+ "x",
1055
+ String(group.count),
1056
+ " | ~",
1057
+ formatTokens(group.repeatedTokens),
1058
+ " saved"
1059
+ ] })
1060
+ ] }),
1061
+ 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 }),
1062
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-7 flex flex-wrap gap-1", children: [
1063
+ visibleSegments.map((duplicateSegment, segmentIndex) => {
1064
+ const target = resolveDuplicateSegment(duplicateSegment, segments);
1065
+ const activate = target === null || onSegmentActivate === void 0 ? void 0 : onSegmentActivate;
1066
+ const label = `${duplicateSegment.label} ~${formatTokens(
1067
+ duplicateSegment.tokens
1068
+ )}`;
1069
+ if (activate === void 0 || target === null) {
1070
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1071
+ "span",
1072
+ {
1073
+ className: "inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground",
1074
+ title: label,
1075
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label })
1076
+ },
1077
+ `${duplicateSegment.path}-${segmentIndex}`
1078
+ );
1079
+ }
1080
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ onClick: () => activate(target),
1085
+ 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",
1086
+ title: "Jump to this duplicate block",
1087
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label })
1088
+ },
1089
+ `${duplicateSegment.path}-${segmentIndex}`
1090
+ );
1091
+ }),
1092
+ 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: [
1093
+ "+",
1094
+ String(hiddenSegmentCount)
1095
+ ] })
1096
+ ] })
1097
+ ]
1098
+ },
1099
+ group.key
1100
+ );
1101
+ }) }),
1102
+ hiddenGroupCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground", children: [
1103
+ "+",
1104
+ String(hiddenGroupCount),
1105
+ " more duplicate group",
1106
+ hiddenGroupCount === 1 ? "" : "s"
1107
+ ] })
1108
+ ] });
1109
+ }
826
1110
  function aggregateByRole(segments) {
827
1111
  const result = [];
828
1112
  for (const role of ROLE_ORDER) {
@@ -853,6 +1137,7 @@ function RequestAnatomy({
853
1137
  onSegmentActivate,
854
1138
  segments: providedSegments
855
1139
  }) {
1140
+ const { providers } = useProviders();
856
1141
  const [viewMode, setViewMode] = reactExports.useState("role");
857
1142
  const segments = reactExports.useMemo(() => providedSegments ?? null, [providedSegments]);
858
1143
  const total = reactExports.useMemo(
@@ -872,9 +1157,10 @@ function RequestAnatomy({
872
1157
  segments,
873
1158
  inputTokens,
874
1159
  parsed,
875
- model
1160
+ model,
1161
+ providers
876
1162
  }),
877
- [inputTokens, model, parsed, segments]
1163
+ [inputTokens, model, parsed, providers, segments]
878
1164
  );
879
1165
  const showDivergenceWarning = reactExports.useMemo(() => {
880
1166
  if (segments === null) return false;
@@ -951,6 +1237,14 @@ function RequestAnatomy({
951
1237
  "Estimate differs from provider-reported input."
952
1238
  ] }),
953
1239
  intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextIntelligenceStrip, { intelligence }),
1240
+ intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
1241
+ DuplicateContentPanel,
1242
+ {
1243
+ groups: intelligence.duplicateGroups,
1244
+ segments,
1245
+ onSegmentActivate
1246
+ }
1247
+ ),
954
1248
  /* @__PURE__ */ jsxRuntimeExports.jsx(
955
1249
  SegmentBar,
956
1250
  {
@@ -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-DiZjOTQT.mjs";
3
- import "./router-Bf0m6F0G.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-DrSMa5O7.mjs";
3
+ import "./router-CSRaa_tu.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-DiZjOTQT.mjs";
3
- import "./router-Bf0m6F0G.mjs";
2
+ import { k as TooltipProvider, l as Tooltip, m as TooltipTrigger, B as Badge, n as TooltipContent, w as JsonViewer } from "./ProxyViewerContainer-DrSMa5O7.mjs";
3
+ import "./router-CSRaa_tu.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-DiZjOTQT.mjs";
1
+ import { P as ProxyViewerContainer } from "./ProxyViewerContainer-DrSMa5O7.mjs";
2
2
  import "../_libs/react.mjs";
3
- import "./router-Bf0m6F0G.mjs";
3
+ import "./router-CSRaa_tu.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-DlAyJ5DB.mjs");
201
+ const { tsrStartManifest } = await import("../_tanstack-start-manifest_v-CfvVUYYj.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-Bf0m6F0G.mjs").then((n) => n.i);
770
+ const routerEntry = await import("./router-CSRaa_tu.mjs").then((n) => n.m);
771
771
  const startEntry = await import("./start-HYkvq4Ni.mjs");
772
772
  return { startEntry, routerEntry };
773
773
  }