@tonyclaw/agent-inspector 2.0.11 → 2.0.12

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-ClM_uVRy.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-CTNNzXSa.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-CwE6I1Pe.js → ReplayDialog-7rp7N_KJ.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-sL2FFo_N.js +1 -0
  6. package/.output/public/assets/ResponseView-Cjlf3b6-.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-CLcLZ7-W.js → StreamingChunkSequence-Ctx3sT3r.js} +1 -1
  8. package/.output/public/assets/_sessionId-DEWzDQ8-.js +1 -0
  9. package/.output/public/assets/index-DGIIhZTn.js +1 -0
  10. package/.output/public/assets/index-D_nZj9Vt.css +1 -0
  11. package/.output/public/assets/{main-D1Xanzu7.js → main-FvIQopAy.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +51 -39
  13. package/.output/server/{_sessionId-D3IyPpws.mjs → _sessionId-BDSgtrsI.mjs} +2 -2
  14. package/.output/server/_ssr/{CompareDrawer-BIMThqxy.mjs → CompareDrawer-Cqvv0I3x.mjs} +3 -3
  15. package/.output/server/_ssr/{ProxyViewerContainer-BiBdKWtj.mjs → ProxyViewerContainer-C0YEoG8N.mjs} +1611 -183
  16. package/.output/server/_ssr/{ReplayDialog-D_wiDyM2.mjs → ReplayDialog-CrsynUKI.mjs} +4 -4
  17. package/.output/server/_ssr/RequestAnatomy-CR_JUkcL.mjs +688 -0
  18. package/.output/server/_ssr/{ResponseView-Drk5ghmL.mjs → ResponseView-Dba2Et69.mjs} +3 -3
  19. package/.output/server/_ssr/{StreamingChunkSequence-F889rmG0.mjs → StreamingChunkSequence-BIMHLstI.mjs} +2 -2
  20. package/.output/server/_ssr/{index-CaPniq9z.mjs → index-DveIUTm5.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-C7S8FRth.mjs → router-D0yXVG0Q.mjs} +85 -22
  23. package/.output/server/{_tanstack-start-manifest_v-CVTkFOdT.mjs → _tanstack-start-manifest_v-pe0xJFuQ.mjs} +1 -1
  24. package/.output/server/index.mjs +62 -62
  25. package/package.json +1 -1
  26. package/src/components/ProxyViewer.tsx +453 -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 +99 -79
  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 {
@@ -97,7 +97,7 @@ function formatRemainingTokens(value: number | null): string {
97
97
 
98
98
  function formatContextLimit(intelligence: ContextIntelligence): string {
99
99
  if (intelligence.contextWindow.tokens === null) return "Unknown";
100
- return formatTokens(intelligence.contextWindow.tokens);
100
+ return formatContextWindowTokens(intelligence.contextWindow.tokens);
101
101
  }
102
102
 
103
103
  function formatContextSource(intelligence: ContextIntelligence): string {
@@ -284,6 +284,8 @@ function DuplicateContentPanel({
284
284
  segments: readonly AnatomySegment[];
285
285
  onSegmentActivate?: (segment: AnatomySegment) => void;
286
286
  }): JSX.Element | null {
287
+ const [expanded, setExpanded] = useState(false);
288
+
287
289
  if (groups.length === 0) return null;
288
290
 
289
291
  const visibleGroups = groups.slice(0, DUPLICATE_GROUP_DISPLAY_COUNT);
@@ -292,87 +294,105 @@ function DuplicateContentPanel({
292
294
 
293
295
  return (
294
296
  <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">
297
+ <button
298
+ type="button"
299
+ onClick={() => setExpanded((current) => !current)}
300
+ aria-expanded={expanded}
301
+ 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"
302
+ >
303
+ <span className="inline-flex min-w-0 items-center gap-1.5">
304
+ {expanded ? (
305
+ <ChevronDown className="size-3.5 shrink-0 text-muted-foreground" />
306
+ ) : (
307
+ <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
308
+ )}
309
+ <span className="truncate text-xs font-medium text-foreground">Duplicate Content</span>
310
+ </span>
311
+ <span className="font-mono text-[11px] text-muted-foreground">
298
312
  {String(groups.length)} group{groups.length === 1 ? "" : "s"} | ~
299
313
  {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>
314
+ </span>
315
+ </button>
316
+
317
+ {expanded && (
318
+ <>
319
+ <div className="space-y-2">
320
+ {visibleGroups.map((group, groupIndex) => {
321
+ const visibleSegments = group.segments.slice(0, DUPLICATE_SEGMENT_DISPLAY_COUNT);
322
+ const hiddenSegmentCount = group.segments.length - visibleSegments.length;
323
+ return (
324
+ <div
325
+ key={group.key}
326
+ className="space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0"
327
+ >
328
+ <div className="flex min-w-0 items-center gap-2 text-xs">
329
+ <span className="w-5 shrink-0 text-right font-mono text-muted-foreground/70">
330
+ {String(groupIndex + 1)}
331
+ </span>
332
+ <span className="min-w-0 flex-1 truncate text-foreground">
333
+ {group.firstLabel}
334
+ </span>
335
+ <span className="shrink-0 font-mono text-muted-foreground">
336
+ x{String(group.count)} | ~{formatTokens(group.repeatedTokens)} saved
337
+ </span>
338
+ </div>
339
+
340
+ {group.preview.length > 0 && (
341
+ <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">
342
+ {group.preview}
343
+ </div>
344
+ )}
321
345
 
322
- {group.preview.length > 0 && (
323
- <div className="ml-7 line-clamp-2 break-words rounded bg-background/40 px-2 py-1 font-mono text-[11px] leading-4 text-muted-foreground">
324
- {group.preview}
325
- </div>
326
- )}
327
-
328
- <div className="ml-7 flex flex-wrap gap-1">
329
- {visibleSegments.map((duplicateSegment, segmentIndex) => {
330
- const target = resolveDuplicateSegment(duplicateSegment, segments);
331
- const activate =
332
- target === null || onSegmentActivate === undefined
333
- ? undefined
334
- : onSegmentActivate;
335
- const label = `${duplicateSegment.label} ~${formatTokens(
336
- duplicateSegment.tokens,
337
- )}`;
338
- if (activate === undefined || target === null) {
339
- return (
340
- <span
341
- key={`${duplicateSegment.path}-${segmentIndex}`}
342
- className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground"
343
- title={label}
344
- >
345
- <span className="truncate">{label}</span>
346
+ <div className="ml-7 flex flex-wrap gap-1">
347
+ {visibleSegments.map((duplicateSegment, segmentIndex) => {
348
+ const target = resolveDuplicateSegment(duplicateSegment, segments);
349
+ const activate =
350
+ target === null || onSegmentActivate === undefined
351
+ ? undefined
352
+ : onSegmentActivate;
353
+ const label = `${duplicateSegment.label} ~${formatTokens(
354
+ duplicateSegment.tokens,
355
+ )}`;
356
+ if (activate === undefined || target === null) {
357
+ return (
358
+ <span
359
+ key={`${duplicateSegment.path}-${segmentIndex}`}
360
+ className="inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground"
361
+ title={label}
362
+ >
363
+ <span className="truncate">{label}</span>
364
+ </span>
365
+ );
366
+ }
367
+ return (
368
+ <button
369
+ key={`${duplicateSegment.path}-${segmentIndex}`}
370
+ type="button"
371
+ onClick={() => activate(target)}
372
+ 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"
373
+ title="Jump to this duplicate block"
374
+ >
375
+ <span className="truncate">{label}</span>
376
+ </button>
377
+ );
378
+ })}
379
+ {hiddenSegmentCount > 0 && (
380
+ <span className="inline-flex h-6 items-center rounded border border-border/40 px-1.5 font-mono text-[11px] text-muted-foreground/70">
381
+ +{String(hiddenSegmentCount)}
346
382
  </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>
383
+ )}
384
+ </div>
385
+ </div>
386
+ );
387
+ })}
388
+ </div>
371
389
 
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>
390
+ {hiddenGroupCount > 0 && (
391
+ <div className="border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground">
392
+ +{String(hiddenGroupCount)} more duplicate group{hiddenGroupCount === 1 ? "" : "s"}
393
+ </div>
394
+ )}
395
+ </>
376
396
  )}
377
397
  </section>
378
398
  );
@@ -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
 
@@ -0,0 +1,196 @@
1
+ import type { ProviderConfig } from "../../../lib/providerContract";
2
+ import type { CapturedLog } from "../../../proxy/schemas";
3
+ import { getLogFormatAdapter, resolveLogFormat } from "../log-formats";
4
+ import {
5
+ analyzeContextIntelligence,
6
+ type ContextHealth,
7
+ type ContextRiskLevel,
8
+ type ContextWindow,
9
+ type RoleUsage,
10
+ } from "./contextIntelligence";
11
+ import type { AnatomySegment } from "./types";
12
+
13
+ export type SessionContextSnapshot = {
14
+ logId: number;
15
+ model: string;
16
+ timestamp: string;
17
+ contextWindow: ContextWindow;
18
+ estimatedInputTokens: number;
19
+ windowUsedTokens: number | null;
20
+ usagePercent: number | null;
21
+ riskLevel: ContextRiskLevel;
22
+ health: ContextHealth;
23
+ diagnostics: string[];
24
+ roleSegments: AnatomySegment[];
25
+ roleTotalTokens: number;
26
+ };
27
+
28
+ export type SessionModelContextSummary = {
29
+ model: string;
30
+ requestCount: number;
31
+ first: SessionContextSnapshot;
32
+ latest: SessionContextSnapshot;
33
+ peak: SessionContextSnapshot;
34
+ trendPercent: number | null;
35
+ };
36
+
37
+ export type SessionContextSummary = {
38
+ models: SessionModelContextSummary[];
39
+ };
40
+
41
+ type SummaryAccumulator = {
42
+ model: string;
43
+ requestCount: number;
44
+ first: SessionContextSnapshot;
45
+ latest: SessionContextSnapshot;
46
+ peak: SessionContextSnapshot;
47
+ };
48
+
49
+ function parseRequestBody(rawBody: string | null): unknown | null {
50
+ if (rawBody === null) return null;
51
+ try {
52
+ const parsed: unknown = JSON.parse(rawBody);
53
+ return parsed;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function roleUsageSegment(usage: RoleUsage): AnatomySegment {
60
+ return {
61
+ role: usage.role,
62
+ label: usage.label,
63
+ size: usage.tokens,
64
+ characters: usage.tokens * 4,
65
+ text: "",
66
+ path: `role:${usage.role}`,
67
+ };
68
+ }
69
+
70
+ function snapshotFromLog(
71
+ log: CapturedLog,
72
+ providers: readonly ProviderConfig[],
73
+ ): SessionContextSnapshot | null {
74
+ const parsed = parseRequestBody(log.rawRequestBody);
75
+ if (parsed === null) return null;
76
+
77
+ const adapter = getLogFormatAdapter(resolveLogFormat(log));
78
+ const segments = adapter.anatomySegments(parsed);
79
+ if (segments === null) return null;
80
+
81
+ const intelligence = analyzeContextIntelligence({
82
+ segments,
83
+ inputTokens: log.inputTokens ?? null,
84
+ parsed,
85
+ model: log.model,
86
+ providers,
87
+ });
88
+ const model = intelligence.model ?? log.model ?? "Unknown model";
89
+ const roleSegments = intelligence.roleUsages.map(roleUsageSegment);
90
+
91
+ return {
92
+ logId: log.id,
93
+ model,
94
+ timestamp: log.timestamp,
95
+ contextWindow: intelligence.contextWindow,
96
+ estimatedInputTokens: intelligence.estimatedInputTokens,
97
+ windowUsedTokens: intelligence.windowUsedTokens,
98
+ usagePercent: intelligence.usagePercent,
99
+ riskLevel: intelligence.riskLevel,
100
+ health: intelligence.health,
101
+ diagnostics: intelligence.diagnostics.map((diagnostic) => diagnostic.title),
102
+ roleSegments,
103
+ roleTotalTokens: roleSegments.reduce((sum, segment) => sum + segment.size, 0),
104
+ };
105
+ }
106
+
107
+ function isHigherPeak(candidate: SessionContextSnapshot, current: SessionContextSnapshot): boolean {
108
+ if (candidate.usagePercent !== null && current.usagePercent !== null) {
109
+ return candidate.usagePercent > current.usagePercent;
110
+ }
111
+ if (candidate.usagePercent !== null && current.usagePercent === null) return true;
112
+ if (candidate.usagePercent === null && current.usagePercent !== null) return false;
113
+ return candidate.estimatedInputTokens > current.estimatedInputTokens;
114
+ }
115
+
116
+ function riskRank(risk: ContextRiskLevel): number {
117
+ switch (risk) {
118
+ case "danger":
119
+ return 4;
120
+ case "watch":
121
+ return 3;
122
+ case "unknown":
123
+ return 2;
124
+ case "ok":
125
+ return 1;
126
+ }
127
+ }
128
+
129
+ function compareModelSummary(
130
+ left: SessionModelContextSummary,
131
+ right: SessionModelContextSummary,
132
+ ): number {
133
+ const riskDelta = riskRank(right.latest.riskLevel) - riskRank(left.latest.riskLevel);
134
+ if (riskDelta !== 0) return riskDelta;
135
+
136
+ const leftPeak = left.peak.usagePercent ?? -1;
137
+ const rightPeak = right.peak.usagePercent ?? -1;
138
+ const peakDelta = rightPeak - leftPeak;
139
+ if (peakDelta !== 0) return peakDelta;
140
+
141
+ return right.latest.logId - left.latest.logId;
142
+ }
143
+
144
+ function trendPercent(
145
+ first: SessionContextSnapshot,
146
+ latest: SessionContextSnapshot,
147
+ ): number | null {
148
+ if (first.usagePercent === null || latest.usagePercent === null) return null;
149
+ return (latest.usagePercent - first.usagePercent) * 100;
150
+ }
151
+
152
+ export function buildSessionContextSummary(
153
+ logs: readonly CapturedLog[],
154
+ providers: readonly ProviderConfig[] = [],
155
+ ): SessionContextSummary {
156
+ const sortedLogs = [...logs].sort((left, right) => left.id - right.id);
157
+ const byModel = new Map<string, SummaryAccumulator>();
158
+
159
+ for (const log of sortedLogs) {
160
+ const snapshot = snapshotFromLog(log, providers);
161
+ if (snapshot === null) continue;
162
+
163
+ const existing = byModel.get(snapshot.model);
164
+ if (existing === undefined) {
165
+ byModel.set(snapshot.model, {
166
+ model: snapshot.model,
167
+ requestCount: 1,
168
+ first: snapshot,
169
+ latest: snapshot,
170
+ peak: snapshot,
171
+ });
172
+ continue;
173
+ }
174
+
175
+ byModel.set(snapshot.model, {
176
+ model: existing.model,
177
+ requestCount: existing.requestCount + 1,
178
+ first: existing.first,
179
+ latest: snapshot,
180
+ peak: isHigherPeak(snapshot, existing.peak) ? snapshot : existing.peak,
181
+ });
182
+ }
183
+
184
+ const models = [...byModel.values()]
185
+ .map((entry) => ({
186
+ model: entry.model,
187
+ requestCount: entry.requestCount,
188
+ first: entry.first,
189
+ latest: entry.latest,
190
+ peak: entry.peak,
191
+ trendPercent: trendPercent(entry.first, entry.latest),
192
+ }))
193
+ .sort(compareModelSummary);
194
+
195
+ return { models };
196
+ }
@@ -0,0 +1,33 @@
1
+ import { safeGetOwnProperty } from "../../lib/objectUtils";
2
+
3
+ export const LOG_FOCUS_REQUEST_EVENT = "agent-inspector:focus-log";
4
+
5
+ export type LogFocusTab = "anatomy" | "request";
6
+
7
+ export type LogFocusRequest = {
8
+ logId: number;
9
+ tab: LogFocusTab;
10
+ };
11
+
12
+ export function dispatchLogFocusRequest(request: LogFocusRequest): void {
13
+ window.dispatchEvent(new CustomEvent(LOG_FOCUS_REQUEST_EVENT, { detail: request }));
14
+ }
15
+
16
+ export function readLogFocusRequest(event: Event): LogFocusRequest | null {
17
+ if (typeof CustomEvent === "undefined" || !(event instanceof CustomEvent)) return null;
18
+
19
+ const detail: unknown = event.detail;
20
+ const logId = safeGetOwnProperty(detail, "logId");
21
+ if (typeof logId !== "number" || !Number.isInteger(logId) || logId < 0) return null;
22
+
23
+ const tab = safeGetOwnProperty(detail, "tab");
24
+ switch (tab) {
25
+ case "anatomy":
26
+ case "request":
27
+ return { logId, tab };
28
+ case undefined:
29
+ return { logId, tab: "request" };
30
+ default:
31
+ return null;
32
+ }
33
+ }
@@ -41,10 +41,19 @@ export type ProviderModelRegistryApplyResult = {
41
41
  };
42
42
 
43
43
  export const BUILTIN_MODEL_METADATA_SOURCE_URL = "agent-inspector://builtin/model-metadata";
44
+ export const HF_DEEPSEEK_V4_PRO_URL = "https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro";
45
+ export const HF_DEEPSEEK_V4_FLASH_URL = "https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash";
46
+ export const HF_MINIMAX_M3_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M3";
47
+ export const HF_MINIMAX_M2_7_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.7";
48
+ export const HF_MINIMAX_M2_5_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.5";
49
+ export const HF_MINIMAX_M2_1_URL = "https://huggingface.co/MiniMaxAI/MiniMax-M2.1";
50
+ export const HF_GLM_5_URL = "https://huggingface.co/zai-org/GLM-5";
51
+ export const HF_GLM_5_1_URL = "https://huggingface.co/zai-org/GLM-5.1";
52
+ export const HF_GLM_5_2_URL = "https://huggingface.co/zai-org/GLM-5.2";
44
53
 
45
54
  export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
46
55
  version: 1,
47
- updatedAt: "2026-06-23T00:00:00.000Z",
56
+ updatedAt: "2026-06-24T00:00:00.000Z",
48
57
  providers: [
49
58
  {
50
59
  name: "DeepSeek",
@@ -54,12 +63,14 @@ export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
54
63
  {
55
64
  id: "deepseek-v4-pro",
56
65
  aliases: ["DeepSeek-V4-Pro"],
57
- contextWindow: 1_000_000,
66
+ contextWindow: 1_048_576,
67
+ sourceUrl: HF_DEEPSEEK_V4_PRO_URL,
58
68
  },
59
69
  {
60
70
  id: "deepseek-v4-flash",
61
71
  aliases: ["DeepSeek-V4-Flash"],
62
- contextWindow: 1_000_000,
72
+ contextWindow: 1_048_576,
73
+ sourceUrl: HF_DEEPSEEK_V4_FLASH_URL,
63
74
  },
64
75
  ],
65
76
  },
@@ -71,37 +82,69 @@ export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
71
82
  {
72
83
  id: "MiniMax M3",
73
84
  aliases: ["MiniMax-M3", "minimax-m3"],
74
- contextWindow: 1_000_000,
85
+ contextWindow: 1_048_576,
86
+ sourceUrl: HF_MINIMAX_M3_URL,
75
87
  },
76
88
  {
77
89
  id: "MiniMax M2.7",
78
90
  aliases: ["MiniMax-M2.7", "minimax-m2.7"],
79
91
  contextWindow: 204_800,
92
+ sourceUrl: HF_MINIMAX_M2_7_URL,
80
93
  },
81
94
  {
82
95
  id: "MiniMax M2.7-highspeed",
83
96
  aliases: ["MiniMax-M2.7-highspeed", "minimax-m2.7-highspeed"],
84
97
  contextWindow: 204_800,
98
+ sourceUrl: HF_MINIMAX_M2_7_URL,
85
99
  },
86
100
  {
87
101
  id: "MiniMax M2.5",
88
102
  aliases: ["MiniMax-M2.5", "minimax-m2.5"],
89
- contextWindow: 204_800,
103
+ contextWindow: 196_608,
104
+ sourceUrl: HF_MINIMAX_M2_5_URL,
90
105
  },
91
106
  {
92
107
  id: "MiniMax M2.5-highspeed",
93
108
  aliases: ["MiniMax-M2.5-highspeed", "minimax-m2.5-highspeed"],
94
- contextWindow: 204_800,
109
+ contextWindow: 196_608,
110
+ sourceUrl: HF_MINIMAX_M2_5_URL,
95
111
  },
96
112
  {
97
113
  id: "MiniMax M2.1",
98
114
  aliases: ["MiniMax-M2.1", "minimax-m2.1"],
99
- contextWindow: 204_800,
115
+ contextWindow: 196_608,
116
+ sourceUrl: HF_MINIMAX_M2_1_URL,
100
117
  },
101
118
  {
102
119
  id: "MiniMax M2.1-highspeed",
103
120
  aliases: ["MiniMax-M2.1-highspeed", "minimax-m2.1-highspeed"],
104
- contextWindow: 204_800,
121
+ contextWindow: 196_608,
122
+ sourceUrl: HF_MINIMAX_M2_1_URL,
123
+ },
124
+ ],
125
+ },
126
+ {
127
+ name: "GLM",
128
+ aliases: ["glm", "zai", "zhipu", "bigmodel"],
129
+ baseUrls: [],
130
+ models: [
131
+ {
132
+ id: "glm-5",
133
+ aliases: ["GLM-5"],
134
+ contextWindow: 202_752,
135
+ sourceUrl: HF_GLM_5_URL,
136
+ },
137
+ {
138
+ id: "glm-5.1",
139
+ aliases: ["GLM-5.1"],
140
+ contextWindow: 202_752,
141
+ sourceUrl: HF_GLM_5_1_URL,
142
+ },
143
+ {
144
+ id: "glm-5.2",
145
+ aliases: ["GLM-5.2"],
146
+ contextWindow: 1_048_576,
147
+ sourceUrl: HF_GLM_5_2_URL,
105
148
  },
106
149
  ],
107
150
  },
@@ -271,8 +314,10 @@ export function applyProviderModelRegistry(
271
314
 
272
315
  const normalized = normalizeModelName(model);
273
316
  const existingMetadata = existing.get(normalized);
317
+ const existingIsManual = existingMetadata?.source === "manual";
274
318
  if (
275
319
  mode === "fill-missing" &&
320
+ existingIsManual &&
276
321
  existingMetadata !== undefined &&
277
322
  existingMetadata.contextWindow !== undefined &&
278
323
  (existingMetadata.outputLimit !== undefined || registryModel.outputLimit === undefined)
@@ -280,12 +325,19 @@ export function applyProviderModelRegistry(
280
325
  continue;
281
326
  }
282
327
 
328
+ const keepManualValue = mode === "fill-missing" && existingIsManual;
283
329
  existing.set(normalized, {
284
330
  model,
285
- contextWindow: existingMetadata?.contextWindow ?? registryModel.contextWindow,
286
- outputLimit: existingMetadata?.outputLimit ?? registryModel.outputLimit,
287
- source: existingMetadata?.source ?? "registry",
288
- sourceUrl: existingMetadata?.sourceUrl ?? registryModel.sourceUrl ?? sourceUrl,
331
+ contextWindow: keepManualValue
332
+ ? (existingMetadata?.contextWindow ?? registryModel.contextWindow)
333
+ : (registryModel.contextWindow ?? existingMetadata?.contextWindow),
334
+ outputLimit: keepManualValue
335
+ ? (existingMetadata?.outputLimit ?? registryModel.outputLimit)
336
+ : (registryModel.outputLimit ?? existingMetadata?.outputLimit),
337
+ source: keepManualValue ? "manual" : "registry",
338
+ sourceUrl: keepManualValue
339
+ ? (existingMetadata?.sourceUrl ?? registryModel.sourceUrl ?? sourceUrl)
340
+ : (registryModel.sourceUrl ?? sourceUrl),
289
341
  updatedAt,
290
342
  });
291
343
  matchedModels.push(model);
package/src/lib/utils.ts CHANGED
@@ -10,6 +10,13 @@ export function formatTokens(count: number): string {
10
10
  return (count / 1000).toFixed(1).replace(/\.0$/, "") + "K";
11
11
  }
12
12
 
13
+ export function formatContextWindowTokens(count: number): string {
14
+ if (count >= 1024 * 1024) {
15
+ return (count / (1024 * 1024)).toFixed(1).replace(/\.0$/, "") + "M";
16
+ }
17
+ return (count / 1024).toFixed(1).replace(/\.0$/, "") + "K";
18
+ }
19
+
13
20
  export type StatusCategory = "success" | "client_error" | "server_error" | "pending";
14
21
 
15
22
  export function getStatusCategory(status: number | null): StatusCategory {