@tonyclaw/agent-inspector 2.0.12 → 2.0.14
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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-ClM_uVRy.js → CompareDrawer-BR_kCD7Q.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-CTNNzXSa.js → ProxyViewerContainer-vc50msJj.js} +17 -17
- package/.output/public/assets/{ReplayDialog-7rp7N_KJ.js → ReplayDialog-DsAYQBHZ.js} +1 -1
- package/.output/public/assets/RequestAnatomy-B4uqyUt8.js +1 -0
- package/.output/public/assets/{ResponseView-Cjlf3b6-.js → ResponseView-C1vQL-fM.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-Ctx3sT3r.js → StreamingChunkSequence-BECMV-xf.js} +1 -1
- package/.output/public/assets/_sessionId-3bjQPmHl.js +1 -0
- package/.output/public/assets/index-_7blCOK0.js +1 -0
- package/.output/public/assets/{main-FvIQopAy.js → main-D6wegveF.js} +2 -2
- package/.output/server/{_sessionId-BDSgtrsI.mjs → _sessionId-CeHY-3xa.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-Cqvv0I3x.mjs → CompareDrawer-blWGaGiT.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-C0YEoG8N.mjs → ProxyViewerContainer-Dt_9BuM9.mjs} +14 -15
- package/.output/server/_ssr/{ReplayDialog-CrsynUKI.mjs → ReplayDialog-mzsq7AjW.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-CR_JUkcL.mjs → RequestAnatomy-CDAcXMBK.mjs} +212 -18
- package/.output/server/_ssr/{ResponseView-Dba2Et69.mjs → ResponseView-B__xXCsZ.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-BIMHLstI.mjs → StreamingChunkSequence-BnJkTdXF.mjs} +2 -2
- package/.output/server/_ssr/{index-DveIUTm5.mjs → index-B2-uF9XT.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D0yXVG0Q.mjs → router-BpEBhA7S.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-pe0xJFuQ.mjs → _tanstack-start-manifest_v-CHekFSzj.mjs} +1 -1
- package/.output/server/index.mjs +68 -68
- package/README.md +110 -0
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +7 -6
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +267 -20
- package/.output/public/assets/RequestAnatomy-sL2FFo_N.js +0 -1
- package/.output/public/assets/_sessionId-DEWzDQ8-.js +0 -1
- package/.output/public/assets/index-DGIIhZTn.js +0 -1
package/README.md
CHANGED
|
@@ -71,6 +71,116 @@ ANTHROPIC_BASE_URL=http://localhost:25947/proxy <tool>
|
|
|
71
71
|
|
|
72
72
|
The web UI runs at http://localhost:25947.
|
|
73
73
|
|
|
74
|
+
## Jenkins Release Webhook
|
|
75
|
+
|
|
76
|
+
The `agent-inspector-release` Jenkins pipeline can be triggered from GitCode by
|
|
77
|
+
using the Jenkins Git plugin `notifyCommit` endpoint. The Jenkins instance must
|
|
78
|
+
be reachable from GitCode; `localhost`, LAN-only addresses, or NAT public IPs
|
|
79
|
+
without port forwarding will not work. Use a public Jenkins URL, a reverse
|
|
80
|
+
proxy, or a tunnel such as Cloudflare Tunnel, frp, or ngrok.
|
|
81
|
+
|
|
82
|
+
Create a GitCode WebHook under the repository settings with these values:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
URL:
|
|
86
|
+
https://<public-jenkins-host>/git/notifyCommit?url=https://gitcode.com/TonyClaw/agent-inspector&token=<jenkins-git-notify-token>
|
|
87
|
+
|
|
88
|
+
WebHook password/signing secret:
|
|
89
|
+
leave empty
|
|
90
|
+
|
|
91
|
+
POST content type:
|
|
92
|
+
application/json
|
|
93
|
+
|
|
94
|
+
Events:
|
|
95
|
+
Push events only
|
|
96
|
+
|
|
97
|
+
Active:
|
|
98
|
+
enabled
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The Jenkins job must have SCM polling enabled so the Git plugin can schedule the
|
|
102
|
+
pipeline after receiving the `notifyCommit` request. Do not commit the real
|
|
103
|
+
Jenkins notify token to the repository.
|
|
104
|
+
|
|
105
|
+
### Local Push Fallback
|
|
106
|
+
|
|
107
|
+
When GitCode cannot reach a local Jenkins instance from the public internet, the
|
|
108
|
+
tracked Husky `pre-push` hook can notify Jenkins from the developer machine that
|
|
109
|
+
runs `git push`.
|
|
110
|
+
|
|
111
|
+
Configure the local secret values once per clone:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
115
|
+
git config --local agentInspector.repositoryUrl https://gitcode.com/TonyClaw/agent-inspector
|
|
116
|
+
git config --local agentInspector.jenkinsNotifyToken <jenkins-git-notify-token>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
On Windows PowerShell:
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
123
|
+
git config --local agentInspector.repositoryUrl https://gitcode.com/TonyClaw/agent-inspector
|
|
124
|
+
git config --local agentInspector.jenkinsNotifyToken <jenkins-git-notify-token>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
After a `main` branch push, the hook waits briefly and then calls Jenkins
|
|
128
|
+
`/git/notifyCommit` from the local machine. The hook can also be configured with
|
|
129
|
+
environment variables instead of local Git config:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
AGENT_INSPECTOR_JENKINS_URL=http://localhost:8080
|
|
133
|
+
AGENT_INSPECTOR_REPOSITORY_URL=https://gitcode.com/TonyClaw/agent-inspector
|
|
134
|
+
AGENT_INSPECTOR_JENKINS_NOTIFY_TOKEN=<jenkins-git-notify-token>
|
|
135
|
+
AGENT_INSPECTOR_JENKINS_PUSH_DELAY_SECONDS=10
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The hook writes local diagnostics to `.git/hooks/jenkins-pre-push.log`. The
|
|
139
|
+
tracked template contains no secret values; every developer must configure their
|
|
140
|
+
own token locally.
|
|
141
|
+
|
|
142
|
+
For repositories that do not use Husky, install the same template as a native Git
|
|
143
|
+
hook:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
cp scripts/hooks/pre-push-jenkins-notify .git/hooks/pre-push
|
|
147
|
+
chmod +x .git/hooks/pre-push
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Verified PR Workflow
|
|
151
|
+
|
|
152
|
+
For local Jenkins instances that GitCode cannot reach, use the verified PR script
|
|
153
|
+
instead of relying on GitCode WebHook delivery. The entire flow is outbound from
|
|
154
|
+
the developer machine:
|
|
155
|
+
|
|
156
|
+
1. Push the current commit to a temporary GitCode branch.
|
|
157
|
+
2. Trigger the local Jenkins job with `GIT_REF=<temporary-branch>`.
|
|
158
|
+
3. Wait for the Jenkins result.
|
|
159
|
+
4. Create a GitCode Pull Request to `main` only when Jenkins returns `SUCCESS`.
|
|
160
|
+
|
|
161
|
+
Configure local secrets once per clone:
|
|
162
|
+
|
|
163
|
+
```powershell
|
|
164
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
165
|
+
git config --local agentInspector.jenkinsJob agent-inspector-release
|
|
166
|
+
git config --local agentInspector.jenkinsUser <jenkins-user>
|
|
167
|
+
git config --local agentInspector.jenkinsApiToken <jenkins-api-token>
|
|
168
|
+
git config --local agentInspector.gitcodeAccessToken <gitcode-access-token>
|
|
169
|
+
git config --local agentInspector.gitcodeOwner TonyClaw
|
|
170
|
+
git config --local agentInspector.gitcodeRepo agent-inspector
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Then run:
|
|
174
|
+
|
|
175
|
+
```powershell
|
|
176
|
+
.\scripts\jenkins\verified-pr.ps1
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The script pushes a branch named like `jenkins-verify/<user>/<sha>`, waits for
|
|
180
|
+
Jenkins, and creates a PR through the GitCode API after a successful build. The
|
|
181
|
+
temporary branch is kept as the PR source branch and is configured for deletion
|
|
182
|
+
after merge when GitCode supports source-branch pruning.
|
|
183
|
+
|
|
74
184
|
## Project Direction
|
|
75
185
|
|
|
76
186
|
Agent Inspector should stay close to the proven inspector codebase while growing into an agent knowledge layer. The memory flow has four layers:
|
package/package.json
CHANGED
|
@@ -157,8 +157,12 @@ function sessionRiskBarClass(risk: ContextRiskLevel): string {
|
|
|
157
157
|
|
|
158
158
|
function formatContextWindow(summary: SessionModelContextSummary): string {
|
|
159
159
|
const tokens = summary.latest.contextWindow.tokens;
|
|
160
|
-
if (tokens === null) return "
|
|
161
|
-
return `
|
|
160
|
+
if (tokens === null) return "Context window unknown";
|
|
161
|
+
return `Context window ${formatContextWindowTokens(tokens)}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatRequestCount(count: number): string {
|
|
165
|
+
return `${count} ${count === 1 ? "request" : "requests"}`;
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
function formatLogReference(logId: number): string {
|
|
@@ -406,11 +410,8 @@ function SessionModelContextRow({ summary }: { summary: SessionModelContextSumma
|
|
|
406
410
|
</span>
|
|
407
411
|
</div>
|
|
408
412
|
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted-foreground">
|
|
409
|
-
<span>{summary.requestCount}
|
|
413
|
+
<span>{formatRequestCount(summary.requestCount)}</span>
|
|
410
414
|
<span>{formatContextWindow(summary)}</span>
|
|
411
|
-
<span className="truncate" title={summary.latest.contextWindow.label}>
|
|
412
|
-
{summary.latest.contextWindow.label}
|
|
413
|
-
</span>
|
|
414
415
|
</div>
|
|
415
416
|
</div>
|
|
416
417
|
|
|
@@ -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)}%`;
|
|
@@ -115,8 +131,8 @@ function formatContextSource(intelligence: ContextIntelligence): string {
|
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
function
|
|
119
|
-
switch (
|
|
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
|
|
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
|
-
{
|
|
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
|
}
|
|
@@ -553,7 +794,13 @@ export function RequestAnatomy({
|
|
|
553
794
|
</div>
|
|
554
795
|
)}
|
|
555
796
|
|
|
556
|
-
{intelligence !== null &&
|
|
797
|
+
{intelligence !== null && (
|
|
798
|
+
<ContextIntelligenceStrip
|
|
799
|
+
intelligence={intelligence}
|
|
800
|
+
segments={segments}
|
|
801
|
+
onSegmentActivate={onSegmentActivate}
|
|
802
|
+
/>
|
|
803
|
+
)}
|
|
557
804
|
|
|
558
805
|
{intelligence !== null && (
|
|
559
806
|
<DuplicateContentPanel
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as p,j as e}from"./main-FvIQopAy.js";import{c as M,u as A,F as U,f as u,s as W,b as f,t as H,v as B,w as G,S as F,G as $,H as q,I as K,K as k,L as V,h as Y}from"./ProxyViewerContainer-CTNNzXSa.js";const J=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],X=M("info",J),Q=.25,Z=5,ee=4,te=6,se=["system","user","assistant","tool","tools"],re={system:"instructions",user:"user turns",assistant:"assistant turns",tool:"tool results",tools:"tool schemas"},ne=[{value:"role",label:"By Role"},{value:"segment",label:"By Segment"}],oe={unknown:"bg-muted-foreground/50",ok:"bg-emerald-500/70",watch:"bg-amber-400/75",danger:"bg-red-500/80"},ae={unknown:"text-muted-foreground",ok:"text-emerald-400",watch:"text-amber-300",danger:"text-red-300"},le={ok:"border-emerald-400/30 bg-emerald-400/8 text-emerald-200",optimizable:"border-amber-400/30 bg-amber-400/8 text-amber-100",risk:"border-red-400/35 bg-red-500/10 text-red-100"},de={ok:"OK",optimizable:"Optimizable",risk:"Risk"};function C(t){return t>=10?`${t.toFixed(0)}%`:t>=1?`${t.toFixed(1)}%`:t>0?"<1%":"0%"}function y(t){return t===null?"Unknown":C(t*100)}function T(t){return t===null?"Unknown":t<0?`-${u(Math.abs(t))}`:u(t)}function E(t){return t.contextWindow.tokens===null?"Unknown":K(t.contextWindow.tokens)}function ie(t){switch(t.contextWindow.source){case"request":return t.contextWindow.label;case"provider":return t.contextWindow.label;case"model-rule":return t.contextWindow.label;case"unknown":return t.model===null?t.contextWindow.label:`${t.contextWindow.label}: ${t.model}`}}function ce(t){switch(t.severity){case"watch":return"border-amber-400/30 bg-amber-400/8 text-amber-100";case"danger":return"border-red-400/35 bg-red-500/10 text-red-100"}}function R(t){const r=de[t.level];if(t.level==="ok")return r;const o=t.level==="risk"?`${String(t.opportunityCount)} signal${t.opportunityCount===1?"":"s"}`:`${String(t.opportunityCount)} opportunit${t.opportunityCount===1?"y":"ies"}`;return t.reclaimableTokens<=0?`${r} | ${o}`:`${r} | ${o} | ~${u(t.reclaimableTokens)} tokens`}function ue({health:t}){return e.jsx("span",{className:f("inline-flex h-5 max-w-full items-center rounded border px-1.5 font-mono text-[10px]",le[t.level]),"aria-label":`Context health ${R(t)}`,children:e.jsx("span",{className:"truncate",children:R(t)})})}function xe({intelligence:t}){const r=t.usagePercent===null?0:Math.max(1,t.usagePercent*100),o=t.outputReserveTokens===null?"No output reserve":`Output reserve ${u(t.outputReserveTokens)}`,a=t.largestRole,x=t.duplicateGroups.length===0?t.diagnostics:t.diagnostics.filter(i=>i.kind!=="duplicate");return e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"grid gap-x-4 gap-y-2 border-y border-border/60 py-2 text-xs sm:grid-cols-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-[10px] font-medium uppercase text-muted-foreground",children:"Window"}),e.jsx("div",{className:"mt-0.5 truncate font-mono text-foreground",children:E(t)}),e.jsx("div",{className:"truncate text-[11px] text-muted-foreground",children:ie(t)})]}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-[10px] font-medium uppercase text-muted-foreground",children:"Used"}),e.jsx("div",{className:f("mt-0.5 font-mono",ae[t.riskLevel]),children:y(t.usagePercent)}),e.jsx("div",{className:"truncate text-[11px] text-muted-foreground",children:o})]}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-[10px] font-medium uppercase text-muted-foreground",children:"Remaining"}),e.jsx("div",{className:"mt-0.5 font-mono text-foreground",children:T(t.remainingAfterReserveTokens)}),e.jsxs("div",{className:"truncate text-[11px] text-muted-foreground",children:["Input only ",T(t.remainingInputTokens)]})]}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"text-[10px] font-medium uppercase text-muted-foreground",children:"Largest"}),a===null?e.jsx("div",{className:"mt-0.5 text-muted-foreground",children:"Unknown"}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mt-0.5 flex min-w-0 items-center gap-1.5",children:[e.jsx("span",{"aria-hidden":"true",className:f("size-2.5 shrink-0 rounded-[2px]",$[a.role])}),e.jsx("span",{className:"truncate text-foreground",children:a.label})]}),e.jsxs("div",{className:"truncate font-mono text-[11px] text-muted-foreground",children:[C(a.percent*100)," | ~",u(a.tokens)]})]})]})]}),t.contextWindow.tokens!==null&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("div",{className:"relative h-2 overflow-hidden rounded-full bg-muted/40",children:[e.jsx("div",{className:f("h-full rounded-full",oe[t.riskLevel]),style:{width:`${String(r)}%`}}),e.jsx("div",{"aria-hidden":"true",className:"absolute top-0 h-full w-px bg-amber-200/70",style:{left:`${String(k.watch*100)}%`}}),e.jsx("div",{"aria-hidden":"true",className:"absolute top-0 h-full w-px bg-red-200/80",style:{left:`${String(k.danger*100)}%`}})]}),e.jsxs("div",{className:"flex justify-between text-[10px] text-muted-foreground",children:[e.jsx("span",{children:"Context window"}),e.jsxs("span",{children:["Watch ",y(k.watch)," / Danger"," ",y(k.danger)]})]})]}),x.length>0&&e.jsx("div",{className:"grid gap-1.5 sm:grid-cols-2",children:x.map(i=>e.jsxs("div",{className:f("rounded border px-2 py-1.5 text-xs",ce(i)),children:[e.jsx("div",{className:"font-medium",children:i.title}),e.jsx("div",{className:"mt-0.5 text-[11px] text-muted-foreground",children:i.detail})]},`${i.kind}-${i.title}`))})]})}function me(t,r){return r.find(o=>o.path===t.path)??null}function fe({groups:t,segments:r,onSegmentActivate:o}){const[a,x]=p.useState(!1);if(t.length===0)return null;const i=t.slice(0,ee),c=t.length-i.length,h=t.reduce((s,l)=>s+l.repeatedTokens,0);return e.jsxs("section",{className:"space-y-2 rounded border border-border/70 bg-muted/15 p-2",children:[e.jsxs("button",{type:"button",onClick:()=>x(s=>!s),"aria-expanded":a,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",children:[e.jsxs("span",{className:"inline-flex min-w-0 items-center gap-1.5",children:[a?e.jsx(V,{className:"size-3.5 shrink-0 text-muted-foreground"}):e.jsx(Y,{className:"size-3.5 shrink-0 text-muted-foreground"}),e.jsx("span",{className:"truncate text-xs font-medium text-foreground",children:"Duplicate Content"})]}),e.jsxs("span",{className:"font-mono text-[11px] text-muted-foreground",children:[String(t.length)," group",t.length===1?"":"s"," | ~",u(h)," repeated"]})]}),a&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"space-y-2",children:i.map((s,l)=>{const b=s.segments.slice(0,te),w=s.segments.length-b.length;return e.jsxs("div",{className:"space-y-1.5 border-t border-border/50 pt-2 first:border-t-0 first:pt-0",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-2 text-xs",children:[e.jsx("span",{className:"w-5 shrink-0 text-right font-mono text-muted-foreground/70",children:String(l+1)}),e.jsx("span",{className:"min-w-0 flex-1 truncate text-foreground",children:s.firstLabel}),e.jsxs("span",{className:"shrink-0 font-mono text-muted-foreground",children:["x",String(s.count)," | ~",u(s.repeatedTokens)," saved"]})]}),s.preview.length>0&&e.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:s.preview}),e.jsxs("div",{className:"ml-7 flex flex-wrap gap-1",children:[b.map((d,g)=>{const v=me(d,r),j=v===null||o===void 0?void 0:o,N=`${d.label} ~${u(d.tokens)}`;return j===void 0||v===null?e.jsx("span",{className:"inline-flex h-6 max-w-40 items-center rounded border border-border/60 px-1.5 text-[11px] text-muted-foreground",title:N,children:e.jsx("span",{className:"truncate",children:N})},`${d.path}-${g}`):e.jsx("button",{type:"button",onClick:()=>j(v),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",title:"Jump to this duplicate block",children:e.jsx("span",{className:"truncate",children:N})},`${d.path}-${g}`)}),w>0&&e.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:["+",String(w)]})]})]},s.key)})}),c>0&&e.jsxs("div",{className:"border-t border-border/50 pt-2 font-mono text-[11px] text-muted-foreground",children:["+",String(c)," more duplicate group",c===1?"":"s"]})]})]})}function pe(t){const r=[];for(const o of se){const a=t.filter(s=>s.role===o);if(a.length===0)continue;const x=a.reduce((s,l)=>s+l.size,0),i=a.reduce((s,l)=>s+l.characters,0),c=q[o],h=`${a.length} segment${a.length===1?"":"s"} grouped as ${re[o]}`;r.push({role:o,label:c,size:x,characters:i,text:h,path:`role:${o}`})}return r}function he(t){return[...t].sort((r,o)=>o.size-r.size).slice(0,Z)}function ve({parsed:t,inputTokens:r,model:o=null,onSegmentActivate:a,segments:x}){const{providers:i}=A(),[c,h]=p.useState("role"),s=p.useMemo(()=>x??null,[x]),l=p.useMemo(()=>(s??[]).reduce((n,m)=>n+m.size,0),[s]),b=p.useMemo(()=>s===null?[]:pe(s),[s]),w=p.useMemo(()=>s===null?[]:he(s),[s]),d=p.useMemo(()=>s===null?null:U({segments:s,inputTokens:r,parsed:t,model:o,providers:i}),[r,o,t,i,s]),g=p.useMemo(()=>s===null||r===null||l===0?!1:Math.abs(r-l)/Math.max(r,l)>=Q,[r,s,l]);if(s===null||s.length===0||t===null&&x===void 0)return null;const v=r!==null&&g?"text-amber-400":"text-muted-foreground",j=c==="role"?b:s,N=l>0?l:j.reduce((n,m)=>n+m.size,0),_=c==="segment"?a:void 0,O=`${String(s.length)} segment${s.length===1?"":"s"}`,P=r===null?"Provider input unknown":`Provider input ${u(r)}`,z=d===null?"Limit unknown":`Limit ${E(d)}`,D=d===null?"Usage unknown":`${y(d.usagePercent)} used`;return e.jsx(W,{delayDuration:150,children:e.jsxs("div",{className:"px-4 py-3 space-y-3","data-testid":"anatomy-root",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[e.jsx("div",{className:"text-sm font-semibold text-foreground",children:"Request Context"}),d!==null&&e.jsx(ue,{health:d.health})]}),e.jsxs("div",{className:f("mt-0.5 text-xs font-mono tabular-nums",v),children:["Estimated ~",u(l)," tokens | ",P," | ",z," |"," ",D," | ",O]})]}),e.jsx("div",{className:"inline-flex shrink-0 rounded border border-border bg-muted/20 p-0.5",role:"group","aria-label":"Context breakdown mode",children:ne.map(n=>e.jsx("button",{type:"button","aria-pressed":c===n.value,onClick:()=>h(n.value),className:f("h-6 rounded-sm px-2 text-[11px] font-medium transition-colors",c===n.value?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"),children:n.label},n.value))})]}),g&&e.jsxs("div",{className:"inline-flex items-center gap-1.5 text-xs text-amber-400",children:[e.jsxs(H,{children:[e.jsx(B,{asChild:!0,children:e.jsx("button",{type:"button",className:"inline-flex items-center hover:text-amber-300","aria-label":"Token estimate differs from provider input",children:e.jsx(X,{className:"size-3.5"})})}),e.jsx(G,{className:"max-w-xs text-xs",children:"The bar uses a local token estimate. Provider input tokens remain the source of truth for billing and context-window usage."})]}),"Estimate differs from provider-reported input."]}),d!==null&&e.jsx(xe,{intelligence:d}),d!==null&&e.jsx(fe,{groups:d.duplicateGroups,segments:s,onSegmentActivate:a}),e.jsx(F,{segments:j,totalTokens:N,showLabels:c==="segment",onActivate:_}),e.jsx("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-1.5 text-[11px] text-muted-foreground",children:b.map(n=>{const m=l>0?n.size/l*100:0;return e.jsxs("div",{className:"inline-flex items-center gap-1.5",children:[e.jsx("span",{"aria-hidden":"true",className:f("size-2.5 rounded-[2px]",$[n.role])}),e.jsx("span",{children:n.label}),e.jsx("span",{className:"font-mono text-muted-foreground/70",children:C(m)})]},n.role)})}),e.jsxs("div",{className:"space-y-1.5",children:[e.jsx("div",{className:"text-xs font-medium text-foreground",children:"Top Contributors"}),e.jsx("div",{className:"grid gap-1",children:w.map((n,m)=>{const I=l>0?n.size/l*100:0,S=a,L=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"w-5 shrink-0 text-right font-mono text-muted-foreground/70",children:String(m+1)}),e.jsx("span",{"aria-hidden":"true",className:f("size-2.5 shrink-0 rounded-[2px]",$[n.role])}),e.jsx("span",{className:"min-w-0 flex-1 truncate text-left",children:n.label}),e.jsxs("span",{className:"shrink-0 font-mono text-muted-foreground",children:[C(I)," | ~",u(n.size)]})]});return S!==void 0?e.jsx("button",{type:"button",onClick:()=>S(n),className:"flex h-7 items-center gap-2 rounded px-1.5 text-xs text-muted-foreground hover:bg-muted/40 hover:text-foreground",title:"Jump to this request block",children:L},`${n.path}-${m}`):e.jsx("div",{className:"flex h-7 items-center gap-2 rounded px-1.5 text-xs text-muted-foreground",children:L},`${n.path}-${m}`)})})]})]})})}export{ve as RequestAnatomy};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as s,j as e}from"./main-FvIQopAy.js";import{P as i}from"./ProxyViewerContainer-CTNNzXSa.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as o}from"./ProxyViewerContainer-CTNNzXSa.js";import"./main-FvIQopAy.js";const r=o;export{r as component};
|