@tonyclaw/agent-inspector 2.0.6 → 2.0.8

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-Djb4XFZS.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-CWdkPZsJ.js +114 -0
  4. package/.output/public/assets/ReplayDialog-DBPMIs2I.js +1 -0
  5. package/.output/public/assets/RequestAnatomy-FBfRNQN7.js +1 -0
  6. package/.output/public/assets/ResponseView-Db4pJL7U.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-BH175O6R.js +1 -0
  8. package/.output/public/assets/_sessionId-3ipVoQtW.js +1 -0
  9. package/.output/public/assets/index-Bo1dGS4R.css +1 -0
  10. package/.output/public/assets/index-CJgaCJo8.js +1 -0
  11. package/.output/public/assets/{main-CKnTJ4-O.js → main-CwhqfJqM.js} +8 -8
  12. package/.output/server/_libs/lucide-react.mjs +170 -108
  13. package/.output/server/_libs/radix-ui__react-collapsible.mjs +2 -2
  14. package/.output/server/{_sessionId-B-x9fRY3.mjs → _sessionId-DzBy9gLa.mjs} +58 -5
  15. package/.output/server/_ssr/{CompareDrawer-BQVNsAY2.mjs → CompareDrawer-C5NBvE2e.mjs} +9 -8
  16. package/.output/server/_ssr/{ProxyViewerContainer-CYm2Dw19.mjs → ProxyViewerContainer-DiZjOTQT.mjs} +857 -32
  17. package/.output/server/_ssr/{ReplayDialog-CaMQBc79.mjs → ReplayDialog-EpvTwFvp.mjs} +7 -8
  18. package/.output/server/_ssr/{RequestAnatomy--P5arRH2.mjs → RequestAnatomy-DaQjhixp.mjs} +512 -7
  19. package/.output/server/_ssr/{ResponseView-RtFwNvgD.mjs → ResponseView-D8tGHCGY.mjs} +5 -74
  20. package/.output/server/_ssr/{StreamingChunkSequence-B5HPkzab.mjs → StreamingChunkSequence-DGQnb1QE.mjs} +8 -7
  21. package/.output/server/_ssr/{index-CZIKZU43.mjs → index-N25hqa7t.mjs} +58 -5
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{router-DGPt3MUc.mjs → router-Bf0m6F0G.mjs} +22 -6
  24. package/.output/server/{_tanstack-start-manifest_v-BzH4pNaI.mjs → _tanstack-start-manifest_v-DlAyJ5DB.mjs} +1 -1
  25. package/.output/server/index.mjs +61 -68
  26. package/package.json +1 -1
  27. package/src/components/proxy-viewer/LogEntry.tsx +17 -2
  28. package/src/components/proxy-viewer/RequestToolsPanel.tsx +292 -0
  29. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +197 -1
  30. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +414 -0
  31. package/src/components/proxy-viewer/requestTools.ts +213 -0
  32. package/src/routes/__root.tsx +27 -0
  33. package/.output/public/assets/CompareDrawer-DDmqSAfl.js +0 -1
  34. package/.output/public/assets/ProxyViewerContainer-Cxpdziwd.js +0 -101
  35. package/.output/public/assets/ReplayDialog-Bt5DGzlh.js +0 -1
  36. package/.output/public/assets/RequestAnatomy-BxX3_N9S.js +0 -1
  37. package/.output/public/assets/ResponseView-Bl_5S9gZ.js +0 -1
  38. package/.output/public/assets/StreamingChunkSequence-RJMwNf6F.js +0 -1
  39. package/.output/public/assets/_sessionId-b4isaoDp.js +0 -1
  40. package/.output/public/assets/index-BZ4x5UI6.js +0 -1
  41. package/.output/public/assets/index-C624DUk9.css +0 -1
  42. package/.output/public/assets/json-viewer-CRL_gWEZ.js +0 -14
  43. package/.output/server/_ssr/json-viewer-d4obyRaA.mjs +0 -515
@@ -1,10 +1,9 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { u as useProviders, D as Dialog, b as DialogContent, d as DialogHeader, e as DialogTitle, T as Tabs, h as TabsList, i as TabsTrigger, j as TabsContent, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent, o as Button } from "./ProxyViewerContainer-CYm2Dw19.mjs";
3
- import { ResponseView } from "./ResponseView-RtFwNvgD.mjs";
4
- import "./router-DGPt3MUc.mjs";
2
+ import { u as useProviders, D as Dialog, b as DialogContent, d as DialogHeader, e as DialogTitle, T as Tabs, h as TabsList, i as TabsTrigger, j as TabsContent, k as TooltipProvider, l as Tooltip, m as TooltipTrigger, n as TooltipContent, o as Button } from "./ProxyViewerContainer-DiZjOTQT.mjs";
3
+ import { ResponseView } from "./ResponseView-D8tGHCGY.mjs";
4
+ import "./router-Bf0m6F0G.mjs";
5
5
  import "../_libs/modelcontextprotocol__server.mjs";
6
6
  import "../_libs/jszip.mjs";
7
- import "./json-viewer-d4obyRaA.mjs";
8
7
  import { I as RotateCcw } from "../_libs/lucide-react.mjs";
9
8
  import { d as object, c as boolean, n as number, b as string } from "../_libs/zod.mjs";
10
9
  import "../_libs/swr.mjs";
@@ -58,9 +57,6 @@ import "../_libs/radix-ui__react-arrow.mjs";
58
57
  import "../_libs/radix-ui__react-use-size.mjs";
59
58
  import "../_libs/radix-ui__react-use-previous.mjs";
60
59
  import "../_libs/@radix-ui/react-visually-hidden+[...].mjs";
61
- import "../_libs/radix-ui__react-tooltip.mjs";
62
- import "../_libs/radix-ui__react-tabs.mjs";
63
- import "../_libs/radix-ui__react-roving-focus.mjs";
64
60
  import "../_libs/react-markdown.mjs";
65
61
  import "../_libs/devlop.mjs";
66
62
  import "../_libs/unified.mjs";
@@ -115,9 +111,12 @@ import "../_libs/inline-style-parser.mjs";
115
111
  import "../_libs/hast-util-whitespace.mjs";
116
112
  import "../_libs/estree-util-is-identifier-name.mjs";
117
113
  import "../_libs/html-url-attributes.mjs";
118
- import "../_libs/radix-ui__react-separator.mjs";
114
+ import "../_libs/radix-ui__react-tooltip.mjs";
115
+ import "../_libs/radix-ui__react-tabs.mjs";
116
+ import "../_libs/radix-ui__react-roving-focus.mjs";
119
117
  import "../_libs/radix-ui__react-collapsible.mjs";
120
118
  import "../_libs/radix-ui__react-scroll-area.mjs";
119
+ import "../_libs/radix-ui__react-separator.mjs";
121
120
  import "../_libs/tanstack__react-router.mjs";
122
121
  import "../_libs/tiny-warning.mjs";
123
122
  import "../_libs/tanstack__router-core.mjs";
@@ -1,9 +1,9 @@
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-CYm2Dw19.mjs";
3
- import "./router-DGPt3MUc.mjs";
4
- import "../_libs/modelcontextprotocol__server.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";
5
4
  import "../_libs/jszip.mjs";
6
- import { a4 as Info } from "../_libs/lucide-react.mjs";
5
+ import "../_libs/modelcontextprotocol__server.mjs";
6
+ import { aa as Info } from "../_libs/lucide-react.mjs";
7
7
  import "../_libs/swr.mjs";
8
8
  import "../_libs/use-sync-external-store.mjs";
9
9
  import "../_libs/dequal.mjs";
@@ -55,10 +55,66 @@ import "../_libs/radix-ui__react-arrow.mjs";
55
55
  import "../_libs/radix-ui__react-use-size.mjs";
56
56
  import "../_libs/radix-ui__react-use-previous.mjs";
57
57
  import "../_libs/@radix-ui/react-visually-hidden+[...].mjs";
58
+ import "../_libs/react-markdown.mjs";
59
+ import "../_libs/devlop.mjs";
60
+ import "../_libs/unified.mjs";
61
+ import "../_libs/bail.mjs";
62
+ import "../_libs/extend.mjs";
63
+ import "../_libs/is-plain-obj.mjs";
64
+ import "../_libs/trough.mjs";
65
+ import "../_libs/vfile.mjs";
66
+ import "../_libs/vfile-message.mjs";
67
+ import "../_libs/unist-util-stringify-position.mjs";
68
+ import "node:process";
69
+ import "node:path";
70
+ import "node:url";
71
+ import "../_libs/remark-parse.mjs";
72
+ import "../_libs/mdast-util-from-markdown.mjs";
73
+ import "../_libs/micromark-util-decode-numeric-character-reference+[...].mjs";
74
+ import "../_libs/micromark-util-decode-string.mjs";
75
+ import "../_libs/decode-named-character-reference+[...].mjs";
76
+ import "../_libs/character-entities.mjs";
77
+ import "../_libs/micromark-util-normalize-identifier+[...].mjs";
78
+ import "../_libs/micromark.mjs";
79
+ import "../_libs/micromark-util-combine-extensions+[...].mjs";
80
+ import "../_libs/micromark-util-chunked.mjs";
81
+ import "../_libs/micromark-factory-space.mjs";
82
+ import "../_libs/micromark-util-character.mjs";
83
+ import "../_libs/micromark-core-commonmark.mjs";
84
+ import "../_libs/micromark-util-classify-character+[...].mjs";
85
+ import "../_libs/micromark-util-resolve-all.mjs";
86
+ import "../_libs/micromark-util-subtokenize.mjs";
87
+ import "../_libs/micromark-factory-destination.mjs";
88
+ import "../_libs/micromark-factory-label.mjs";
89
+ import "../_libs/micromark-factory-title.mjs";
90
+ import "../_libs/micromark-factory-whitespace.mjs";
91
+ import "../_libs/micromark-util-html-tag-name.mjs";
92
+ import "../_libs/mdast-util-to-string.mjs";
93
+ import "../_libs/remark-rehype.mjs";
94
+ import "../_libs/mdast-util-to-hast.mjs";
95
+ import "../_libs/ungap__structured-clone.mjs";
96
+ import "../_libs/micromark-util-sanitize-uri.mjs";
97
+ import "../_libs/unist-util-position.mjs";
98
+ import "../_libs/trim-lines.mjs";
99
+ import "../_libs/unist-util-visit.mjs";
100
+ import "../_libs/unist-util-visit-parents.mjs";
101
+ import "../_libs/unist-util-is.mjs";
102
+ import "../_libs/hast-util-to-jsx-runtime.mjs";
103
+ import "../_libs/comma-separated-tokens.mjs";
104
+ import "../_libs/property-information.mjs";
105
+ import "../_libs/space-separated-tokens.mjs";
106
+ import "../_libs/style-to-js.mjs";
107
+ import "../_libs/style-to-object.mjs";
108
+ import "../_libs/inline-style-parser.mjs";
109
+ import "../_libs/hast-util-whitespace.mjs";
110
+ import "../_libs/estree-util-is-identifier-name.mjs";
111
+ import "../_libs/html-url-attributes.mjs";
58
112
  import "../_libs/zod.mjs";
59
113
  import "../_libs/radix-ui__react-tooltip.mjs";
60
114
  import "../_libs/radix-ui__react-tabs.mjs";
61
115
  import "../_libs/radix-ui__react-roving-focus.mjs";
116
+ import "../_libs/radix-ui__react-collapsible.mjs";
117
+ import "../_libs/radix-ui__react-scroll-area.mjs";
62
118
  import "../_libs/tanstack__react-router.mjs";
63
119
  import "../_libs/tiny-warning.mjs";
64
120
  import "../_libs/tanstack__router-core.mjs";
@@ -73,12 +129,9 @@ import "../_libs/isbot.mjs";
73
129
  import "node:fs";
74
130
  import "node:fs/promises";
75
131
  import "node:buffer";
76
- import "node:path";
77
132
  import "node:child_process";
78
- import "node:url";
79
133
  import "../_libs/conf.mjs";
80
134
  import "node:util";
81
- import "node:process";
82
135
  import "node:crypto";
83
136
  import "node:assert";
84
137
  import "../_libs/dot-prop.mjs";
@@ -112,6 +165,297 @@ import "../_libs/lie.mjs";
112
165
  import "../_libs/immediate.mjs";
113
166
  import "../_libs/setimmediate.mjs";
114
167
  import "../_libs/pako.mjs";
168
+ const WATCH_USAGE_PERCENT = 0.75;
169
+ const DANGER_USAGE_PERCENT = 0.9;
170
+ const LONG_TOOL_SCHEMA_TOKENS = 8e3;
171
+ const LARGE_ROLE_PERCENT = 0.35;
172
+ const DUPLICATE_MIN_TOKENS = 40;
173
+ const DUPLICATE_MIN_CHARS = 160;
174
+ const HISTORY_BLOAT_SEGMENTS = 18;
175
+ const HISTORY_BLOAT_TOKENS = 6e4;
176
+ const HISTORY_BLOAT_PERCENT = 0.7;
177
+ const SYSTEM_HEAVY_TOKENS = 3e3;
178
+ const SYSTEM_HEAVY_PERCENT = 0.3;
179
+ const EXPLICIT_CONTEXT_WINDOW_FIELDS = [
180
+ "context_window",
181
+ "contextWindow",
182
+ "context_length",
183
+ "contextLength",
184
+ "max_context_length",
185
+ "maxContextLength",
186
+ "max_input_tokens",
187
+ "maxInputTokens"
188
+ ];
189
+ const OUTPUT_RESERVE_FIELDS = [
190
+ "max_tokens",
191
+ "maxTokens",
192
+ "max_completion_tokens",
193
+ "maxCompletionTokens",
194
+ "max_output_tokens",
195
+ "maxOutputTokens"
196
+ ];
197
+ const MODEL_CONTEXT_RULES = [
198
+ {
199
+ family: "OpenAI GPT-4.1",
200
+ tokens: 1047576,
201
+ mode: "prefix",
202
+ patterns: ["gpt-4.1"]
203
+ },
204
+ {
205
+ family: "OpenAI GPT-4o",
206
+ tokens: 128e3,
207
+ mode: "prefix",
208
+ patterns: ["gpt-4o"]
209
+ },
210
+ {
211
+ family: "OpenAI o-series",
212
+ tokens: 128e3,
213
+ mode: "prefix",
214
+ patterns: ["o1", "o3", "o4"]
215
+ },
216
+ {
217
+ family: "Anthropic Claude",
218
+ tokens: 2e5,
219
+ mode: "prefix",
220
+ patterns: ["claude-"]
221
+ },
222
+ {
223
+ family: "DeepSeek",
224
+ tokens: 64e3,
225
+ mode: "prefix",
226
+ patterns: ["deepseek-"]
227
+ },
228
+ {
229
+ family: "MiniMax",
230
+ tokens: 1e6,
231
+ mode: "includes",
232
+ patterns: ["minimax"]
233
+ }
234
+ ];
235
+ const ROLE_LABELS = {
236
+ system: "System",
237
+ user: "User messages",
238
+ assistant: "Assistant messages",
239
+ tool: "Tool results",
240
+ tools: "Tool definitions"
241
+ };
242
+ function readPositiveInteger(value) {
243
+ if (typeof value === "number") {
244
+ if (Number.isFinite(value) && value > 0) return Math.floor(value);
245
+ return null;
246
+ }
247
+ if (typeof value === "string") {
248
+ const parsed = Number(value);
249
+ if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
250
+ }
251
+ return null;
252
+ }
253
+ function readPositiveIntegerField(parsed, fields) {
254
+ for (const field of fields) {
255
+ const value = readPositiveInteger(safeGetOwnProperty(parsed, field));
256
+ if (value !== null) return value;
257
+ }
258
+ return null;
259
+ }
260
+ function readRequestModel(parsed) {
261
+ const value = safeGetOwnProperty(parsed, "model");
262
+ if (typeof value !== "string") return null;
263
+ const trimmed = value.trim();
264
+ return trimmed === "" ? null : trimmed;
265
+ }
266
+ function matchesRule(model, rule) {
267
+ switch (rule.mode) {
268
+ case "prefix":
269
+ return rule.patterns.some((pattern) => model.startsWith(pattern));
270
+ case "includes":
271
+ return rule.patterns.some((pattern) => model.includes(pattern));
272
+ }
273
+ }
274
+ function resolveContextWindow(model, parsed) {
275
+ const explicitWindow = readPositiveIntegerField(parsed, EXPLICIT_CONTEXT_WINDOW_FIELDS);
276
+ if (explicitWindow !== null) {
277
+ return {
278
+ tokens: explicitWindow,
279
+ label: "Request metadata",
280
+ source: "request"
281
+ };
282
+ }
283
+ if (model === null) {
284
+ return {
285
+ tokens: null,
286
+ label: "Unknown model",
287
+ source: "unknown"
288
+ };
289
+ }
290
+ const normalized = model.trim().toLowerCase();
291
+ for (const rule of MODEL_CONTEXT_RULES) {
292
+ if (matchesRule(normalized, rule)) {
293
+ return {
294
+ tokens: rule.tokens,
295
+ label: rule.family,
296
+ source: "model-rule"
297
+ };
298
+ }
299
+ }
300
+ return {
301
+ tokens: null,
302
+ label: "No local rule",
303
+ source: "unknown"
304
+ };
305
+ }
306
+ function clampPercent(value) {
307
+ if (value < 0) return 0;
308
+ if (value > 1) return 1;
309
+ return value;
310
+ }
311
+ function riskLevel(usagePercent) {
312
+ if (usagePercent === null) return "unknown";
313
+ if (usagePercent >= DANGER_USAGE_PERCENT) return "danger";
314
+ if (usagePercent >= WATCH_USAGE_PERCENT) return "watch";
315
+ return "ok";
316
+ }
317
+ function sumSegments(segments, role) {
318
+ return segments.filter((segment) => segment.role === role).reduce((sum, segment) => sum + segment.size, 0);
319
+ }
320
+ function buildRoleUsages(segments, total) {
321
+ const roles = ["system", "user", "assistant", "tool", "tools"];
322
+ return roles.map((role) => {
323
+ const tokens = sumSegments(segments, role);
324
+ return {
325
+ role,
326
+ label: ROLE_LABELS[role],
327
+ tokens,
328
+ percent: total > 0 ? tokens / total : 0
329
+ };
330
+ }).filter((usage) => usage.tokens > 0).sort((a, b) => b.tokens - a.tokens);
331
+ }
332
+ function normalizeDuplicateText(text) {
333
+ return text.replace(/\s+/g, " ").trim().toLowerCase();
334
+ }
335
+ function duplicateDiagnostic(segments) {
336
+ const buckets = /* @__PURE__ */ new Map();
337
+ 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) {
344
+ buckets.set(normalized, {
345
+ count: 1,
346
+ tokens: segment.size,
347
+ firstTokens: segment.size,
348
+ label: segment.label
349
+ });
350
+ continue;
351
+ }
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
+ }
359
+ let repeatedGroups = 0;
360
+ let repeatedSegments = 0;
361
+ let repeatedTokens = 0;
362
+ let largest = null;
363
+ 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;
369
+ }
370
+ if (repeatedGroups === 0 || largest === null) return null;
371
+ return {
372
+ kind: "duplicate",
373
+ severity: repeatedTokens >= LONG_TOOL_SCHEMA_TOKENS ? "danger" : "watch",
374
+ title: "Duplicate context",
375
+ detail: `${String(repeatedSegments)} repeated blocks across ${String(
376
+ repeatedGroups
377
+ )} group${repeatedGroups === 1 ? "" : "s"}, ~${String(repeatedTokens)} repeated tokens.`
378
+ };
379
+ }
380
+ function diagnosticsForRolePressure(roleUsages, total, messageSegmentCount) {
381
+ const diagnostics = [];
382
+ const toolUsage = roleUsages.find((usage) => usage.role === "tools") ?? null;
383
+ if (toolUsage !== null && (toolUsage.tokens >= LONG_TOOL_SCHEMA_TOKENS || toolUsage.percent >= LARGE_ROLE_PERCENT && toolUsage.tokens >= SYSTEM_HEAVY_TOKENS)) {
384
+ diagnostics.push({
385
+ kind: "tool-schema",
386
+ severity: toolUsage.tokens >= LONG_TOOL_SCHEMA_TOKENS ? "danger" : "watch",
387
+ title: "Large tool schema",
388
+ detail: `Tool definitions take ${Math.round(toolUsage.percent * 100)}% of request context.`
389
+ });
390
+ }
391
+ const systemUsage = roleUsages.find((usage) => usage.role === "system") ?? null;
392
+ if (systemUsage !== null && systemUsage.tokens >= SYSTEM_HEAVY_TOKENS && systemUsage.percent >= SYSTEM_HEAVY_PERCENT) {
393
+ diagnostics.push({
394
+ kind: "system-heavy",
395
+ severity: "watch",
396
+ title: "Large system prompt",
397
+ detail: `System instructions take ${Math.round(systemUsage.percent * 100)}% of request context.`
398
+ });
399
+ }
400
+ const historyTokens = roleUsages.filter((usage) => usage.role === "user" || usage.role === "assistant" || usage.role === "tool").reduce((sum, usage) => sum + usage.tokens, 0);
401
+ const historyPercent = total > 0 ? historyTokens / total : 0;
402
+ if (messageSegmentCount >= HISTORY_BLOAT_SEGMENTS && (historyTokens >= HISTORY_BLOAT_TOKENS || historyPercent >= HISTORY_BLOAT_PERCENT)) {
403
+ diagnostics.push({
404
+ kind: "history-bloat",
405
+ severity: historyPercent >= 0.85 ? "danger" : "watch",
406
+ title: "Conversation history growth",
407
+ detail: `${String(messageSegmentCount)} message blocks take ${Math.round(
408
+ historyPercent * 100
409
+ )}% of request context.`
410
+ });
411
+ }
412
+ return diagnostics;
413
+ }
414
+ function analyzeContextIntelligence({
415
+ segments,
416
+ inputTokens,
417
+ parsed,
418
+ model
419
+ }) {
420
+ const totalEstimatedTokens = segments.reduce((sum, segment) => sum + segment.size, 0);
421
+ const requestModel = model ?? readRequestModel(parsed);
422
+ const estimatedInputTokens = inputTokens ?? totalEstimatedTokens;
423
+ const outputReserveTokens = readPositiveIntegerField(parsed, OUTPUT_RESERVE_FIELDS);
424
+ const contextWindow = resolveContextWindow(requestModel, parsed);
425
+ const reservedTokens = outputReserveTokens ?? 0;
426
+ const windowUsedTokens = contextWindow.tokens === null ? null : estimatedInputTokens + reservedTokens;
427
+ const remainingInputTokens = contextWindow.tokens === null ? null : contextWindow.tokens - estimatedInputTokens;
428
+ const remainingAfterReserveTokens = contextWindow.tokens === null ? null : contextWindow.tokens - estimatedInputTokens - reservedTokens;
429
+ const usagePercent = contextWindow.tokens === null || windowUsedTokens === null ? null : clampPercent(windowUsedTokens / contextWindow.tokens);
430
+ const roleUsages = buildRoleUsages(segments, totalEstimatedTokens);
431
+ const largestRole = roleUsages[0] ?? null;
432
+ const messageSegmentCount = segments.filter((segment) => segment.role !== "tools").length;
433
+ const duplicate = duplicateDiagnostic(segments);
434
+ const diagnostics = diagnosticsForRolePressure(
435
+ roleUsages,
436
+ totalEstimatedTokens,
437
+ messageSegmentCount
438
+ );
439
+ if (duplicate !== null) diagnostics.unshift(duplicate);
440
+ return {
441
+ model: requestModel,
442
+ contextWindow,
443
+ estimatedInputTokens,
444
+ outputReserveTokens,
445
+ windowUsedTokens,
446
+ remainingInputTokens,
447
+ remainingAfterReserveTokens,
448
+ usagePercent,
449
+ riskLevel: riskLevel(usagePercent),
450
+ roleUsages,
451
+ largestRole,
452
+ diagnostics
453
+ };
454
+ }
455
+ const CONTEXT_USAGE_THRESHOLDS = {
456
+ watch: WATCH_USAGE_PERCENT,
457
+ danger: DANGER_USAGE_PERCENT
458
+ };
115
459
  const ANATOMY_ROLE_LABELS = {
116
460
  system: "System",
117
461
  user: "User",
@@ -330,12 +674,155 @@ const VIEW_MODE_OPTIONS = [
330
674
  { value: "role", label: "By Role" },
331
675
  { value: "segment", label: "By Segment" }
332
676
  ];
677
+ const RISK_CLASS = {
678
+ unknown: "bg-muted-foreground/50",
679
+ ok: "bg-emerald-500/70",
680
+ watch: "bg-amber-400/75",
681
+ danger: "bg-red-500/80"
682
+ };
683
+ const RISK_TEXT_CLASS = {
684
+ unknown: "text-muted-foreground",
685
+ ok: "text-emerald-400",
686
+ watch: "text-amber-300",
687
+ danger: "text-red-300"
688
+ };
333
689
  function formatPercent(value) {
334
690
  if (value >= 10) return `${value.toFixed(0)}%`;
335
691
  if (value >= 1) return `${value.toFixed(1)}%`;
336
692
  if (value > 0) return "<1%";
337
693
  return "0%";
338
694
  }
695
+ function formatUsagePercent(value) {
696
+ if (value === null) return "Unknown";
697
+ return formatPercent(value * 100);
698
+ }
699
+ function formatRemainingTokens(value) {
700
+ if (value === null) return "Unknown";
701
+ if (value < 0) return `-${formatTokens(Math.abs(value))}`;
702
+ return formatTokens(value);
703
+ }
704
+ function formatContextLimit(intelligence) {
705
+ if (intelligence.contextWindow.tokens === null) return "Unknown";
706
+ return formatTokens(intelligence.contextWindow.tokens);
707
+ }
708
+ function formatContextSource(intelligence) {
709
+ switch (intelligence.contextWindow.source) {
710
+ case "request":
711
+ return intelligence.contextWindow.label;
712
+ case "model-rule":
713
+ return intelligence.contextWindow.label;
714
+ case "unknown":
715
+ return intelligence.model === null ? intelligence.contextWindow.label : `${intelligence.contextWindow.label}: ${intelligence.model}`;
716
+ }
717
+ }
718
+ function diagnosticTone(diagnostic) {
719
+ switch (diagnostic.severity) {
720
+ case "watch":
721
+ return "border-amber-400/30 bg-amber-400/8 text-amber-100";
722
+ case "danger":
723
+ return "border-red-400/35 bg-red-500/10 text-red-100";
724
+ }
725
+ }
726
+ function ContextIntelligenceStrip({
727
+ intelligence
728
+ }) {
729
+ const usageWidth = intelligence.usagePercent === null ? 0 : Math.max(1, intelligence.usagePercent * 100);
730
+ const outputReserveLabel = intelligence.outputReserveTokens === null ? "No output reserve" : `Output reserve ${formatTokens(intelligence.outputReserveTokens)}`;
731
+ const largestRole = intelligence.largestRole;
732
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
733
+ /* @__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
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
735
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: "Window" }),
736
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 truncate font-mono text-foreground", children: formatContextLimit(intelligence) }),
737
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: formatContextSource(intelligence) })
738
+ ] }),
739
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
740
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: "Used" }),
741
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("mt-0.5 font-mono", RISK_TEXT_CLASS[intelligence.riskLevel]), children: formatUsagePercent(intelligence.usagePercent) }),
742
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: outputReserveLabel })
743
+ ] }),
744
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
745
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: "Remaining" }),
746
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 font-mono text-foreground", children: formatRemainingTokens(intelligence.remainingAfterReserveTokens) }),
747
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "truncate text-[11px] text-muted-foreground", children: [
748
+ "Input only ",
749
+ formatRemainingTokens(intelligence.remainingInputTokens)
750
+ ] })
751
+ ] }),
752
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
753
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] font-medium uppercase text-muted-foreground", children: "Largest" }),
754
+ largestRole === null ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-muted-foreground", children: "Unknown" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
755
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-0.5 flex min-w-0 items-center gap-1.5", children: [
756
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
757
+ "span",
758
+ {
759
+ "aria-hidden": "true",
760
+ className: cn(
761
+ "size-2.5 shrink-0 rounded-[2px]",
762
+ ROLE_COLOR_CLASSES[largestRole.role]
763
+ )
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate text-foreground", children: largestRole.label })
767
+ ] }),
768
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "truncate font-mono text-[11px] text-muted-foreground", children: [
769
+ formatPercent(largestRole.percent * 100),
770
+ " | ~",
771
+ formatTokens(largestRole.tokens)
772
+ ] })
773
+ ] })
774
+ ] })
775
+ ] }),
776
+ intelligence.contextWindow.tokens !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [
777
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-2 overflow-hidden rounded-full bg-muted/40", children: [
778
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
779
+ "div",
780
+ {
781
+ className: cn("h-full rounded-full", RISK_CLASS[intelligence.riskLevel]),
782
+ style: { width: `${String(usageWidth)}%` }
783
+ }
784
+ ),
785
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
786
+ "div",
787
+ {
788
+ "aria-hidden": "true",
789
+ className: "absolute top-0 h-full w-px bg-amber-200/70",
790
+ style: { left: `${String(CONTEXT_USAGE_THRESHOLDS.watch * 100)}%` }
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
794
+ "div",
795
+ {
796
+ "aria-hidden": "true",
797
+ className: "absolute top-0 h-full w-px bg-red-200/80",
798
+ style: { left: `${String(CONTEXT_USAGE_THRESHOLDS.danger * 100)}%` }
799
+ }
800
+ )
801
+ ] }),
802
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-muted-foreground", children: [
803
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Context window" }),
804
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
805
+ "Watch ",
806
+ formatUsagePercent(CONTEXT_USAGE_THRESHOLDS.watch),
807
+ " / Danger",
808
+ " ",
809
+ formatUsagePercent(CONTEXT_USAGE_THRESHOLDS.danger)
810
+ ] })
811
+ ] })
812
+ ] }),
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(
814
+ "div",
815
+ {
816
+ className: cn("rounded border px-2 py-1.5 text-xs", diagnosticTone(diagnostic)),
817
+ children: [
818
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: diagnostic.title }),
819
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-[11px] text-muted-foreground", children: diagnostic.detail })
820
+ ]
821
+ },
822
+ `${diagnostic.kind}-${diagnostic.title}`
823
+ )) })
824
+ ] });
825
+ }
339
826
  function aggregateByRole(segments) {
340
827
  const result = [];
341
828
  for (const role of ROLE_ORDER) {
@@ -362,6 +849,7 @@ function topContributors(segments) {
362
849
  function RequestAnatomy({
363
850
  parsed,
364
851
  inputTokens,
852
+ model = null,
365
853
  onSegmentActivate,
366
854
  segments: providedSegments
367
855
  }) {
@@ -379,6 +867,15 @@ function RequestAnatomy({
379
867
  () => segments === null ? [] : topContributors(segments),
380
868
  [segments]
381
869
  );
870
+ const intelligence = reactExports.useMemo(
871
+ () => segments === null ? null : analyzeContextIntelligence({
872
+ segments,
873
+ inputTokens,
874
+ parsed,
875
+ model
876
+ }),
877
+ [inputTokens, model, parsed, segments]
878
+ );
382
879
  const showDivergenceWarning = reactExports.useMemo(() => {
383
880
  if (segments === null) return false;
384
881
  if (inputTokens === null) return false;
@@ -395,6 +892,8 @@ function RequestAnatomy({
395
892
  const activateSegment = viewMode === "segment" ? onSegmentActivate : void 0;
396
893
  const segmentCountLabel = `${String(segments.length)} segment${segments.length === 1 ? "" : "s"}`;
397
894
  const providerInputLabel = inputTokens === null ? "Provider input unknown" : `Provider input ${formatTokens(inputTokens)}`;
895
+ const contextLimitLabel = intelligence === null ? "Limit unknown" : `Limit ${formatContextLimit(intelligence)}`;
896
+ const usageLabel = intelligence === null ? "Usage unknown" : `${formatUsagePercent(intelligence.usagePercent)} used`;
398
897
  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: [
399
898
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
400
899
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
@@ -405,6 +904,11 @@ function RequestAnatomy({
405
904
  " tokens | ",
406
905
  providerInputLabel,
407
906
  " | ",
907
+ contextLimitLabel,
908
+ " |",
909
+ " ",
910
+ usageLabel,
911
+ " | ",
408
912
  segmentCountLabel
409
913
  ] })
410
914
  ] }),
@@ -446,6 +950,7 @@ function RequestAnatomy({
446
950
  ] }),
447
951
  "Estimate differs from provider-reported input."
448
952
  ] }),
953
+ intelligence !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextIntelligenceStrip, { intelligence }),
449
954
  /* @__PURE__ */ jsxRuntimeExports.jsx(
450
955
  SegmentBar,
451
956
  {