@tonyclaw/agent-inspector 2.0.11 → 2.0.13

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 (47) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-C9OWdxHM.js → CompareDrawer-CrMyE23D.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-BZuo0px3.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-CwE6I1Pe.js → ReplayDialog-TQGhDdTa.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-D6kQYtfa.js +1 -0
  6. package/.output/public/assets/ResponseView-C6ybWuB8.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-CLcLZ7-W.js → StreamingChunkSequence-Bvs59_-I.js} +1 -1
  8. package/.output/public/assets/_sessionId-BNG_mnaH.js +1 -0
  9. package/.output/public/assets/index-BUxIwHhC.js +1 -0
  10. package/.output/public/assets/index-D_nZj9Vt.css +1 -0
  11. package/.output/public/assets/{main-D1Xanzu7.js → main-DvFSJlOd.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +51 -39
  13. package/.output/server/{_sessionId-D3IyPpws.mjs → _sessionId-DHYJxPb7.mjs} +2 -2
  14. package/.output/server/_ssr/{CompareDrawer-BIMThqxy.mjs → CompareDrawer-meimDBeA.mjs} +3 -3
  15. package/.output/server/_ssr/{ProxyViewerContainer-BiBdKWtj.mjs → ProxyViewerContainer-Co-WKq6u.mjs} +1610 -183
  16. package/.output/server/_ssr/{ReplayDialog-D_wiDyM2.mjs → ReplayDialog-cclcGyR9.mjs} +4 -4
  17. package/.output/server/_ssr/RequestAnatomy-to_rezzu.mjs +882 -0
  18. package/.output/server/_ssr/{ResponseView-Drk5ghmL.mjs → ResponseView-CZZI4IZd.mjs} +3 -3
  19. package/.output/server/_ssr/{StreamingChunkSequence-F889rmG0.mjs → StreamingChunkSequence-BTF4xSYX.mjs} +2 -2
  20. package/.output/server/_ssr/{index-CaPniq9z.mjs → index-D2fpJFP5.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-C7S8FRth.mjs → router-RDBAF-Iu.mjs} +85 -22
  23. package/.output/server/{_tanstack-start-manifest_v-CVTkFOdT.mjs → _tanstack-start-manifest_v-DB9sseoH.mjs} +1 -1
  24. package/.output/server/index.mjs +58 -58
  25. package/package.json +1 -1
  26. package/src/components/ProxyViewer.tsx +454 -52
  27. package/src/components/providers/ProviderCard.tsx +45 -2
  28. package/src/components/providers/ProviderForm.tsx +245 -80
  29. package/src/components/providers/ProvidersPanel.tsx +7 -1
  30. package/src/components/proxy-viewer/ConversationGroup.tsx +18 -1
  31. package/src/components/proxy-viewer/LogEntry.tsx +25 -0
  32. package/src/components/proxy-viewer/TurnGroup.tsx +15 -0
  33. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +366 -99
  34. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +22 -4
  35. package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +196 -0
  36. package/src/components/proxy-viewer/logFocus.ts +33 -0
  37. package/src/lib/providerModelMetadata.ts +64 -12
  38. package/src/lib/utils.ts +7 -0
  39. package/src/proxy/providers.ts +25 -0
  40. package/src/routes/api/providers.ts +3 -0
  41. package/.output/public/assets/ProxyViewerContainer-CNzay4u8.js +0 -114
  42. package/.output/public/assets/RequestAnatomy-DEehC3yz.js +0 -1
  43. package/.output/public/assets/ResponseView-CE5UsuUq.js +0 -1
  44. package/.output/public/assets/_sessionId-DE__tPTo.js +0 -1
  45. package/.output/public/assets/index-BFO4Jmw5.js +0 -1
  46. package/.output/public/assets/index-CDzZ7l_S.css +0 -1
  47. package/.output/server/_ssr/RequestAnatomy-5UiMTESr.mjs +0 -1383
@@ -1,6 +1,6 @@
1
- import { Info } from "lucide-react";
1
+ import { ChevronDown, ChevronRight, Info } from "lucide-react";
2
2
  import { type JSX, useMemo, useState } from "react";
3
- import { cn, formatTokens } from "../../../lib/utils";
3
+ import { cn, formatContextWindowTokens, formatTokens } from "../../../lib/utils";
4
4
  import { useProviders } from "../../../lib/useProviders";
5
5
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
6
6
  import {
@@ -36,6 +36,9 @@ const DIVERGENCE_AMBER_THRESHOLD = 0.25;
36
36
  const TOP_CONTRIBUTOR_COUNT = 5;
37
37
  const DUPLICATE_GROUP_DISPLAY_COUNT = 4;
38
38
  const DUPLICATE_SEGMENT_DISPLAY_COUNT = 6;
39
+ const TOOL_SCHEMA_SAVINGS_RATIO = 0.25;
40
+ const SYSTEM_PROMPT_SAVINGS_RATIO = 0.2;
41
+ const HISTORY_SAVINGS_RATIO = 0.2;
39
42
  const ROLE_ORDER: AnatomyRole[] = ["system", "user", "assistant", "tool", "tools"];
40
43
 
41
44
  const ROLE_DESCRIPTIONS: Record<AnatomyRole, string> = {
@@ -77,6 +80,19 @@ const HEALTH_LABEL: Record<ContextHealthLevel, string> = {
77
80
  risk: "Risk",
78
81
  };
79
82
 
83
+ type ContextAdvisorKind = ContextDiagnostic["kind"] | "context-pressure" | "context-window";
84
+
85
+ type ContextAdvisorItem = {
86
+ key: string;
87
+ kind: ContextAdvisorKind;
88
+ severity: "watch" | "danger";
89
+ title: string;
90
+ impact: string;
91
+ action: string;
92
+ savingTokens: number | null;
93
+ target: AnatomySegment | null;
94
+ };
95
+
80
96
  function formatPercent(value: number): string {
81
97
  if (value >= 10) return `${value.toFixed(0)}%`;
82
98
  if (value >= 1) return `${value.toFixed(1)}%`;
@@ -97,7 +113,7 @@ function formatRemainingTokens(value: number | null): string {
97
113
 
98
114
  function formatContextLimit(intelligence: ContextIntelligence): string {
99
115
  if (intelligence.contextWindow.tokens === null) return "Unknown";
100
- return formatTokens(intelligence.contextWindow.tokens);
116
+ return formatContextWindowTokens(intelligence.contextWindow.tokens);
101
117
  }
102
118
 
103
119
  function formatContextSource(intelligence: ContextIntelligence): string {
@@ -115,8 +131,8 @@ function formatContextSource(intelligence: ContextIntelligence): string {
115
131
  }
116
132
  }
117
133
 
118
- function diagnosticTone(diagnostic: ContextDiagnostic): string {
119
- switch (diagnostic.severity) {
134
+ function advisorTone(severity: ContextAdvisorItem["severity"]): string {
135
+ switch (severity) {
120
136
  case "watch":
121
137
  return "border-amber-400/30 bg-amber-400/8 text-amber-100";
122
138
  case "danger":
@@ -151,10 +167,250 @@ function ContextHealthBadge({ health }: { health: ContextHealth }): JSX.Element
151
167
  );
152
168
  }
153
169
 
170
+ function roleUsageTokens(intelligence: ContextIntelligence, role: AnatomyRole): number {
171
+ return intelligence.roleUsages.find((usage) => usage.role === role)?.tokens ?? 0;
172
+ }
173
+
174
+ function roleUsagePercent(intelligence: ContextIntelligence, role: AnatomyRole): number {
175
+ return intelligence.roleUsages.find((usage) => usage.role === role)?.percent ?? 0;
176
+ }
177
+
178
+ function estimatedSaving(tokens: number, ratio: number): number | null {
179
+ if (tokens <= 0) return null;
180
+ const saving = Math.round(tokens * ratio);
181
+ return saving > 0 ? saving : null;
182
+ }
183
+
184
+ function largestSegmentForRole(
185
+ segments: readonly AnatomySegment[],
186
+ role: AnatomyRole,
187
+ ): AnatomySegment | null {
188
+ return (
189
+ [...segments]
190
+ .filter((segment) => segment.role === role)
191
+ .sort((left, right) => right.size - left.size)[0] ?? null
192
+ );
193
+ }
194
+
195
+ function largestSegmentForHistory(segments: readonly AnatomySegment[]): AnatomySegment | null {
196
+ return (
197
+ [...segments]
198
+ .filter(
199
+ (segment) =>
200
+ segment.role === "user" || segment.role === "assistant" || segment.role === "tool",
201
+ )
202
+ .sort((left, right) => right.size - left.size)[0] ?? null
203
+ );
204
+ }
205
+
206
+ function firstDuplicateTarget(
207
+ groups: readonly DuplicateContextGroup[],
208
+ segments: readonly AnatomySegment[],
209
+ ): AnatomySegment | null {
210
+ const firstGroup = groups[0] ?? null;
211
+ if (firstGroup === null) return null;
212
+ const firstSegment = firstGroup.segments[0] ?? null;
213
+ if (firstSegment === null) return null;
214
+ return resolveDuplicateSegment(firstSegment, segments);
215
+ }
216
+
217
+ function duplicateSaving(groups: readonly DuplicateContextGroup[]): number | null {
218
+ const saving = groups.reduce((sum, group) => sum + group.repeatedTokens, 0);
219
+ return saving > 0 ? saving : null;
220
+ }
221
+
222
+ function historyTokens(intelligence: ContextIntelligence): number {
223
+ return (
224
+ roleUsageTokens(intelligence, "user") +
225
+ roleUsageTokens(intelligence, "assistant") +
226
+ roleUsageTokens(intelligence, "tool")
227
+ );
228
+ }
229
+
230
+ function advisorItemFromDiagnostic({
231
+ diagnostic,
232
+ intelligence,
233
+ segments,
234
+ }: {
235
+ diagnostic: ContextDiagnostic;
236
+ intelligence: ContextIntelligence;
237
+ segments: readonly AnatomySegment[];
238
+ }): ContextAdvisorItem {
239
+ switch (diagnostic.kind) {
240
+ case "duplicate":
241
+ return {
242
+ key: "duplicate",
243
+ kind: "duplicate",
244
+ severity: diagnostic.severity,
245
+ title: "Duplicate content",
246
+ impact: diagnostic.detail,
247
+ action: "Remove repeated blocks or keep one canonical copy in the next request.",
248
+ savingTokens: duplicateSaving(intelligence.duplicateGroups),
249
+ target: firstDuplicateTarget(intelligence.duplicateGroups, segments),
250
+ };
251
+ case "tool-schema": {
252
+ const tokens = roleUsageTokens(intelligence, "tools");
253
+ const percent = roleUsagePercent(intelligence, "tools");
254
+ return {
255
+ key: "tool-schema",
256
+ kind: "tool-schema",
257
+ severity: diagnostic.severity,
258
+ title: "Tool schema pressure",
259
+ impact: `Tool definitions use ~${formatTokens(tokens)} tokens (${formatPercent(
260
+ percent * 100,
261
+ )}).`,
262
+ action: "Disable unused tools or shorten verbose schema descriptions.",
263
+ savingTokens: estimatedSaving(tokens, TOOL_SCHEMA_SAVINGS_RATIO),
264
+ target: largestSegmentForRole(segments, "tools"),
265
+ };
266
+ }
267
+ case "system-heavy": {
268
+ const tokens = roleUsageTokens(intelligence, "system");
269
+ const percent = roleUsagePercent(intelligence, "system");
270
+ return {
271
+ key: "system-heavy",
272
+ kind: "system-heavy",
273
+ severity: diagnostic.severity,
274
+ title: "Large system prompt",
275
+ impact: `System instructions use ~${formatTokens(tokens)} tokens (${formatPercent(
276
+ percent * 100,
277
+ )}).`,
278
+ action: "Compress repeated policy text and move stable guidance into reusable memory.",
279
+ savingTokens: estimatedSaving(tokens, SYSTEM_PROMPT_SAVINGS_RATIO),
280
+ target: largestSegmentForRole(segments, "system"),
281
+ };
282
+ }
283
+ case "history-bloat": {
284
+ const tokens = historyTokens(intelligence);
285
+ return {
286
+ key: "history-bloat",
287
+ kind: "history-bloat",
288
+ severity: diagnostic.severity,
289
+ title: "Conversation history growth",
290
+ impact: diagnostic.detail,
291
+ action: "Summarize older turns and trim resolved tool outputs before continuing.",
292
+ savingTokens: estimatedSaving(tokens, HISTORY_SAVINGS_RATIO),
293
+ target: largestSegmentForHistory(segments),
294
+ };
295
+ }
296
+ }
297
+ }
298
+
299
+ function buildContextAdvisorItems({
300
+ intelligence,
301
+ segments,
302
+ }: {
303
+ intelligence: ContextIntelligence;
304
+ segments: readonly AnatomySegment[];
305
+ }): ContextAdvisorItem[] {
306
+ const items: ContextAdvisorItem[] = [];
307
+
308
+ if (intelligence.riskLevel === "watch" || intelligence.riskLevel === "danger") {
309
+ items.push({
310
+ key: "context-pressure",
311
+ kind: "context-pressure",
312
+ severity: intelligence.riskLevel === "danger" ? "danger" : "watch",
313
+ title: "Context window pressure",
314
+ impact: `${formatUsagePercent(intelligence.usagePercent)} used, ${formatRemainingTokens(
315
+ intelligence.remainingAfterReserveTokens,
316
+ )} remaining after output reserve.`,
317
+ action: "Start with the largest context block, then remove duplicates or summarize history.",
318
+ savingTokens: null,
319
+ target: [...segments].sort((left, right) => right.size - left.size)[0] ?? null,
320
+ });
321
+ }
322
+
323
+ if (intelligence.contextWindow.source === "unknown") {
324
+ items.push({
325
+ key: "context-window",
326
+ kind: "context-window",
327
+ severity: "watch",
328
+ title: "Context window unknown",
329
+ impact: "Agent Inspector cannot calculate remaining space for this model.",
330
+ action: "Add model metadata in Provider settings so risk and remaining space are accurate.",
331
+ savingTokens: null,
332
+ target: null,
333
+ });
334
+ }
335
+
336
+ intelligence.diagnostics.forEach((diagnostic) => {
337
+ items.push(advisorItemFromDiagnostic({ diagnostic, intelligence, segments }));
338
+ });
339
+
340
+ return items;
341
+ }
342
+
343
+ function ContextAdvisorPanel({
344
+ items,
345
+ onSegmentActivate,
346
+ }: {
347
+ items: readonly ContextAdvisorItem[];
348
+ onSegmentActivate?: (segment: AnatomySegment) => void;
349
+ }): JSX.Element | null {
350
+ if (items.length === 0) return null;
351
+
352
+ return (
353
+ <section className="space-y-2 rounded border border-border/70 bg-muted/15 p-2">
354
+ <div className="flex flex-wrap items-center justify-between gap-2">
355
+ <div className="text-xs font-medium text-foreground">Context Advisor</div>
356
+ <div className="font-mono text-[11px] text-muted-foreground">
357
+ {String(items.length)} issue{items.length === 1 ? "" : "s"}
358
+ </div>
359
+ </div>
360
+ <div className="grid gap-2">
361
+ {items.map((item) => {
362
+ const target = item.target;
363
+ const canJump = target !== null && onSegmentActivate !== undefined;
364
+ return (
365
+ <div
366
+ key={item.key}
367
+ className={cn("rounded border px-2 py-1.5 text-xs", advisorTone(item.severity))}
368
+ >
369
+ <div className="flex min-w-0 flex-wrap items-start justify-between gap-2">
370
+ <div className="min-w-0">
371
+ <div className="font-medium">{item.title}</div>
372
+ <div className="mt-0.5 text-[11px] text-muted-foreground">
373
+ Impact: {item.impact}
374
+ </div>
375
+ <div className="mt-0.5 text-[11px] text-muted-foreground">
376
+ Action: {item.action}
377
+ </div>
378
+ </div>
379
+ <div className="flex shrink-0 items-center gap-1.5">
380
+ {item.savingTokens !== null && (
381
+ <span className="font-mono text-[11px] text-muted-foreground">
382
+ Save ~{formatTokens(item.savingTokens)}
383
+ </span>
384
+ )}
385
+ {canJump && target !== null && onSegmentActivate !== undefined && (
386
+ <button
387
+ type="button"
388
+ onClick={() => onSegmentActivate(target)}
389
+ className="inline-flex h-6 items-center gap-1 rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground hover:border-border hover:bg-muted/40 hover:text-foreground"
390
+ title="Jump to the most relevant context block"
391
+ >
392
+ <ChevronRight className="size-3" />
393
+ Jump
394
+ </button>
395
+ )}
396
+ </div>
397
+ </div>
398
+ </div>
399
+ );
400
+ })}
401
+ </div>
402
+ </section>
403
+ );
404
+ }
405
+
154
406
  function ContextIntelligenceStrip({
155
407
  intelligence,
408
+ segments,
409
+ onSegmentActivate,
156
410
  }: {
157
411
  intelligence: ContextIntelligence;
412
+ segments: readonly AnatomySegment[];
413
+ onSegmentActivate?: (segment: AnatomySegment) => void;
158
414
  }): JSX.Element {
159
415
  const usageWidth =
160
416
  intelligence.usagePercent === null ? 0 : Math.max(1, intelligence.usagePercent * 100);
@@ -163,10 +419,7 @@ function ContextIntelligenceStrip({
163
419
  ? "No output reserve"
164
420
  : `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
165
421
  const largestRole = intelligence.largestRole;
166
- const visibleDiagnostics =
167
- intelligence.duplicateGroups.length === 0
168
- ? intelligence.diagnostics
169
- : intelligence.diagnostics.filter((diagnostic) => diagnostic.kind !== "duplicate");
422
+ const advisorItems = buildContextAdvisorItems({ intelligence, segments });
170
423
 
171
424
  return (
172
425
  <div className="space-y-2">
@@ -251,19 +504,7 @@ function ContextIntelligenceStrip({
251
504
  </div>
252
505
  )}
253
506
 
254
- {visibleDiagnostics.length > 0 && (
255
- <div className="grid gap-1.5 sm:grid-cols-2">
256
- {visibleDiagnostics.map((diagnostic) => (
257
- <div
258
- key={`${diagnostic.kind}-${diagnostic.title}`}
259
- className={cn("rounded border px-2 py-1.5 text-xs", diagnosticTone(diagnostic))}
260
- >
261
- <div className="font-medium">{diagnostic.title}</div>
262
- <div className="mt-0.5 text-[11px] text-muted-foreground">{diagnostic.detail}</div>
263
- </div>
264
- ))}
265
- </div>
266
- )}
507
+ <ContextAdvisorPanel items={advisorItems} onSegmentActivate={onSegmentActivate} />
267
508
  </div>
268
509
  );
269
510
  }
@@ -284,6 +525,8 @@ function DuplicateContentPanel({
284
525
  segments: readonly AnatomySegment[];
285
526
  onSegmentActivate?: (segment: AnatomySegment) => void;
286
527
  }): JSX.Element | null {
528
+ const [expanded, setExpanded] = useState(false);
529
+
287
530
  if (groups.length === 0) return null;
288
531
 
289
532
  const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
@@ -292,87 +535,105 @@ function DuplicateContentPanel({
292
535
 
293
536
  return (
294
537
  <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">
538
+ <button
539
+ type="button"
540
+ onClick={() => setExpanded((current) => !current)}
541
+ aria-expanded={expanded}
542
+ className="flex w-full flex-wrap items-center justify-between gap-2 rounded px-1 py-0.5 text-left transition-colors hover:bg-muted/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
543
+ >
544
+ <span className="inline-flex min-w-0 items-center gap-1.5">
545
+ {expanded ? (
546
+ <ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
547
+ ) : (
548
+ <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
549
+ )}
550
+ <span className="truncate text-xs font-medium text-foreground">Duplicate Content</span>
551
+ </span>
552
+ <span className="font-mono text-[11px] text-muted-foreground">
298
553
  {String(groups.length)} group{groups.length === 1 ? "" : "s"} | ~
299
554
  {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>
555
+ </span>
556
+ </button>
557
+
558
+ {expanded && (
559
+ <>
560
+ <div className="space-y-2">
561
+ {visibleGroups.map((group, groupIndex) => {
562
+ const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
563
+ const hiddenSegmentCount = group.segments.length - visibleSegments.length;
564
+ return (
565
+ <div
566
+ key={group.key}
567
+ className="space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0"
568
+ >
569
+ <div className="flex min-w-0 items-center gap-2 text-xs">
570
+ <span className="w-5 shrink-0 text-right font-mono text-muted-foreground/70">
571
+ {String(groupIndex + 1)}
572
+ </span>
573
+ <span className="min-w-0 flex-1 truncate text-foreground">
574
+ {group.firstLabel}
575
+ </span>
576
+ <span className="shrink-0 font-mono text-muted-foreground">
577
+ x{String(group.count)} | ~{formatTokens(group.repeatedTokens)} saved
578
+ </span>
579
+ </div>
580
+
581
+ {group.preview.length > 0 && (
582
+ <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">
583
+ {group.preview}
584
+ </div>
585
+ )}
321
586
 
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>
587
+ <div className="ml-7 flex flex-wrap gap-1">
588
+ {visibleSegments.map((duplicateSegment, segmentIndex) => {
589
+ const target = resolveDuplicateSegment(duplicateSegment, segments);
590
+ const activate =
591
+ target === null || onSegmentActivate === undefined
592
+ ? undefined
593
+ : onSegmentActivate;
594
+ const label = `${duplicateSegment.label} ~${formatTokens(
595
+ duplicateSegment.tokens,
596
+ )}`;
597
+ if (activate === undefined || target === null) {
598
+ return (
599
+ <span
600
+ key={`${duplicateSegment.path}-${segmentIndex}`}
601
+ className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground"
602
+ title={label}
603
+ >
604
+ <span className="truncate">{label}</span>
605
+ </span>
606
+ );
607
+ }
608
+ return (
609
+ <button
610
+ key={`${duplicateSegment.path}-${segmentIndex}`}
611
+ type="button"
612
+ onClick={() => activate(target)}
613
+ 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"
614
+ title="Jump to this duplicate block"
615
+ >
616
+ <span className="truncate">{label}</span>
617
+ </button>
618
+ );
619
+ })}
620
+ {hiddenSegmentCount > 0 && (
621
+ <span className="inline-flex h-6 items-center rounded border border-border/40 px-1.5 font-mono text-[11px] text-muted-foreground/70">
622
+ +{String(hiddenSegmentCount)}
346
623
  </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>
624
+ )}
625
+ </div>
626
+ </div>
627
+ );
628
+ })}
629
+ </div>
371
630
 
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>
631
+ {hiddenGroupCount > 0 && (
632
+ <div className="border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground">
633
+ +{String(hiddenGroupCount)} more duplicate group{hiddenGroupCount === 1 ? "" : "s"}
634
+ </div>
635
+ )}
636
+ </>
376
637
  )}
377
638
  </section>
378
639
  );
@@ -533,7 +794,13 @@ export function RequestAnatomy({
533
794
  </div>
534
795
  )}
535
796
 
536
- {intelligence !== null && <ContextIntelligenceStrip intelligence={intelligence} />}
797
+ {intelligence !== null && (
798
+ <ContextIntelligenceStrip
799
+ intelligence={intelligence}
800
+ segments={segments}
801
+ onSegmentActivate={onSegmentActivate}
802
+ />
803
+ )}
537
804
 
538
805
  {intelligence !== null && (
539
806
  <DuplicateContentPanel
@@ -167,7 +167,7 @@ const MODEL_CONTEXT_RULES: readonly ModelContextRule[] = [
167
167
  },
168
168
  {
169
169
  family: "DeepSeek V4",
170
- tokens: 1_000_000,
170
+ tokens: 1_048_576,
171
171
  mode: "prefix",
172
172
  patterns: ["deepseek-v4-pro", "deepseek-v4-flash"],
173
173
  },
@@ -179,15 +179,33 @@ const MODEL_CONTEXT_RULES: readonly ModelContextRule[] = [
179
179
  },
180
180
  {
181
181
  family: "MiniMax M3",
182
- tokens: 1_000_000,
182
+ tokens: 1_048_576,
183
183
  mode: "includes",
184
184
  patterns: ["minimax-m3"],
185
185
  },
186
186
  {
187
- family: "MiniMax M2",
187
+ family: "MiniMax M2.7",
188
188
  tokens: 204_800,
189
189
  mode: "includes",
190
- patterns: ["minimax-m2.7", "minimax-m2.5", "minimax-m2.1"],
190
+ patterns: ["minimax-m2.7"],
191
+ },
192
+ {
193
+ family: "MiniMax M2.5/M2.1",
194
+ tokens: 196_608,
195
+ mode: "includes",
196
+ patterns: ["minimax-m2.5", "minimax-m2.1"],
197
+ },
198
+ {
199
+ family: "GLM-5.2",
200
+ tokens: 1_048_576,
201
+ mode: "prefix",
202
+ patterns: ["glm-5.2"],
203
+ },
204
+ {
205
+ family: "GLM-5",
206
+ tokens: 202_752,
207
+ mode: "prefix",
208
+ patterns: ["glm-5", "glm-5.1"],
191
209
  },
192
210
  ];
193
211