@tonyclaw/agent-inspector 2.1.12 → 2.1.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/cli.js +0 -8
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DjgjIFx7.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +106 -0
- package/.output/public/assets/{ReplayDialog-Cd1VsfRe.js → ReplayDialog-CU0Tbb2c.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-QxSpbmsj.js → RequestAnatomy-BNahe83D.js} +1 -1
- package/.output/public/assets/ResponseView-DSOnGqi6.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-CwNGqJ5n.js → StreamingChunkSequence-BEKTDklB.js} +1 -1
- package/.output/public/assets/_sessionId-Cif8JZdn.js +1 -0
- package/.output/public/assets/index-DWOkqdCa.js +14 -0
- package/.output/public/assets/index-D_WfwzUi.js +1 -0
- package/.output/public/assets/index-DtLuQrs0.css +1 -0
- package/.output/public/assets/json-viewer-C2JpgcW0.js +1 -0
- package/.output/public/assets/{main-B_EcGyf-.js → main-CSONBwwn.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +20 -20
- package/.output/server/_libs/react-markdown.mjs +90 -2
- package/.output/server/{_sessionId-CmO9read.mjs → _sessionId-CPAa37n5.mjs} +6 -57
- package/.output/server/_ssr/{CompareDrawer-D9wf870o.mjs → CompareDrawer-ceW5VxMo.mjs} +17 -62
- package/.output/server/_ssr/{ProxyViewerContainer-BDLJSawP.mjs → ProxyViewerContainer-CDfEE_w-.mjs} +872 -688
- package/.output/server/_ssr/{ReplayDialog-B2dS4kmP.mjs → ReplayDialog-V0s_eEbR.mjs} +8 -59
- package/.output/server/_ssr/{RequestAnatomy-k49Peshi.mjs → RequestAnatomy-f1ccwR9d.mjs} +6 -57
- package/.output/server/_ssr/{ResponseView-Bk3B1suu.mjs → ResponseView-BIRrqG4H.mjs} +138 -60
- package/.output/server/_ssr/{StreamingChunkSequence-D6MDGCgh.mjs → StreamingChunkSequence-V3JFjCgX.mjs} +13 -58
- package/.output/server/_ssr/{index-CC8KdlTb.mjs → index-DsykulzS.mjs} +6 -57
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-Dcnm0Ivf.mjs +478 -0
- package/.output/server/_ssr/{router-BEmMnVa7.mjs → router-4bdm6Mt2.mjs} +674 -173
- package/.output/server/{_tanstack-start-manifest_v-Bf_4hG4w.mjs → _tanstack-start-manifest_v-Xp4CO64V.mjs} +1 -1
- package/.output/server/index.mjs +70 -56
- package/package.json +1 -1
- package/src/components/ProxyViewerContainer.tsx +25 -17
- package/src/components/providers/ImportWizardDialog.tsx +7 -1
- package/src/components/providers/ProviderCard.tsx +10 -1
- package/src/components/providers/ProviderForm.tsx +157 -41
- package/src/components/providers/ProvidersPanel.tsx +5 -1
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
- package/src/components/proxy-viewer/CompareDrawer.tsx +20 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
- package/src/components/proxy-viewer/LogEntry.tsx +31 -11
- package/src/components/proxy-viewer/LogEntryHeader.tsx +111 -40
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
- package/src/components/proxy-viewer/TurnGroup.tsx +53 -7
- package/src/components/proxy-viewer/TurnGroupList.tsx +153 -0
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +74 -2
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -7
- package/src/components/proxy-viewer/formats/index.tsx +10 -3
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +185 -17
- package/src/components/proxy-viewer/log-formats/openai.ts +107 -15
- package/src/components/proxy-viewer/logFocus.ts +15 -2
- package/src/components/proxy-viewer/requestTools.ts +7 -3
- package/src/components/proxy-viewer/viewerState.ts +72 -3
- package/src/components/ui/json-viewer-bulk.ts +41 -6
- package/src/components/ui/json-viewer.tsx +9 -8
- package/src/contracts/index.ts +15 -1
- package/src/contracts/openai.ts +94 -0
- package/src/lib/providerContract.ts +1 -0
- package/src/lib/providerModelMetadata.ts +7 -1
- package/src/lib/stopReason.ts +12 -0
- package/src/lib/upstreamUrl.ts +46 -0
- package/src/mcp/previewExtractor.ts +73 -3
- package/src/mcp/server.ts +2 -0
- package/src/mcp/toolHandlers.ts +2 -0
- package/src/proxy/constants.ts +2 -0
- package/src/proxy/formats/openai/handler.ts +40 -9
- package/src/proxy/formats/openai/index.ts +7 -0
- package/src/proxy/formats/openai/schemas.ts +15 -1
- package/src/proxy/formats/openai/stream.ts +529 -135
- package/src/proxy/formats/registry.ts +9 -1
- package/src/proxy/identityProxy.ts +0 -7
- package/src/proxy/providerImporters.ts +45 -5
- package/src/proxy/providers.ts +31 -11
- package/src/proxy/schemas.ts +6 -0
- package/src/proxy/toolSchemaWarnings.ts +25 -3
- package/src/proxy/upstream.ts +18 -37
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +2 -0
- package/.output/public/assets/CompareDrawer-XTdSOto9.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-Cc944N8i.js +0 -117
- package/.output/public/assets/ResponseView-8GivXCzp.js +0 -1
- package/.output/public/assets/_sessionId-D89uDTQs.js +0 -1
- package/.output/public/assets/index-BsFdgXac.js +0 -1
- package/.output/public/assets/index-CmtfjQPv.css +0 -1
|
@@ -34,15 +34,48 @@ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/
|
|
|
34
34
|
import type { CacheTrend } from "./cacheTrend";
|
|
35
35
|
|
|
36
36
|
const STATUS_BADGE_CLASSES: Record<StatusCategory, string> = {
|
|
37
|
-
success: "bg-emerald-
|
|
38
|
-
client_error: "bg-amber-
|
|
39
|
-
server_error: "bg-rose-
|
|
40
|
-
pending: "bg-
|
|
37
|
+
success: "bg-emerald-400/[0.08] text-emerald-200 border-emerald-300/25",
|
|
38
|
+
client_error: "bg-amber-300/[0.10] text-amber-100 border-amber-300/30",
|
|
39
|
+
server_error: "bg-rose-400/[0.10] text-rose-100 border-rose-300/30",
|
|
40
|
+
pending: "bg-cyan-300/[0.08] text-cyan-100 border-cyan-300/25",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const STATUS_CUE_CLASSES: Record<
|
|
44
|
+
StatusCategory,
|
|
45
|
+
{
|
|
46
|
+
frame: string;
|
|
47
|
+
dot: string;
|
|
48
|
+
rail: string;
|
|
49
|
+
}
|
|
50
|
+
> = {
|
|
51
|
+
success: {
|
|
52
|
+
frame: "border-emerald-300/20 bg-emerald-300/[0.06] text-emerald-100",
|
|
53
|
+
dot: "bg-emerald-300 shadow-[0_0_14px_rgba(110,231,183,0.8)]",
|
|
54
|
+
rail: "bg-emerald-300/45 shadow-[0_0_22px_rgba(110,231,183,0.28)]",
|
|
55
|
+
},
|
|
56
|
+
client_error: {
|
|
57
|
+
frame: "border-amber-300/25 bg-amber-300/[0.08] text-amber-100",
|
|
58
|
+
dot: "bg-amber-200 shadow-[0_0_14px_rgba(252,211,77,0.8)]",
|
|
59
|
+
rail: "bg-amber-300/50 shadow-[0_0_22px_rgba(252,211,77,0.28)]",
|
|
60
|
+
},
|
|
61
|
+
server_error: {
|
|
62
|
+
frame: "border-rose-300/25 bg-rose-300/[0.08] text-rose-100",
|
|
63
|
+
dot: "bg-rose-300 shadow-[0_0_14px_rgba(253,164,175,0.8)]",
|
|
64
|
+
rail: "bg-rose-300/50 shadow-[0_0_22px_rgba(253,164,175,0.28)]",
|
|
65
|
+
},
|
|
66
|
+
pending: {
|
|
67
|
+
frame: "border-cyan-300/25 bg-cyan-300/[0.08] text-cyan-100",
|
|
68
|
+
dot: "bg-cyan-200 shadow-[0_0_14px_rgba(103,232,249,0.8)]",
|
|
69
|
+
rail: "bg-cyan-300/50 shadow-[0_0_22px_rgba(103,232,249,0.28)]",
|
|
70
|
+
},
|
|
41
71
|
};
|
|
42
72
|
|
|
43
73
|
const HEADER_ACTION_BUTTON_CLASS =
|
|
44
74
|
"size-8 border-white/10 bg-[#0a0d11] text-muted-foreground shadow-none hover:border-cyan-300/30 hover:bg-cyan-400/10 hover:text-cyan-100";
|
|
45
75
|
|
|
76
|
+
const TELEMETRY_CHIP_CLASS =
|
|
77
|
+
"flex h-7 items-center gap-1 rounded-md border border-white/10 bg-black/20 px-2 text-xs shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]";
|
|
78
|
+
|
|
46
79
|
function formatElapsed(ms: number): string {
|
|
47
80
|
if (ms < 1000) return `${ms}ms`;
|
|
48
81
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -53,6 +86,19 @@ function formatTokenRate(tokensPerSecond: number): string {
|
|
|
53
86
|
return `${tokensPerSecond.toFixed(1)}/s`;
|
|
54
87
|
}
|
|
55
88
|
|
|
89
|
+
function statusCueLabel(category: StatusCategory, status: number | null): string {
|
|
90
|
+
switch (category) {
|
|
91
|
+
case "success":
|
|
92
|
+
return status === null ? "OK" : String(status);
|
|
93
|
+
case "client_error":
|
|
94
|
+
return status === null ? "WARN" : String(status);
|
|
95
|
+
case "server_error":
|
|
96
|
+
return status === null ? "FAIL" : String(status);
|
|
97
|
+
case "pending":
|
|
98
|
+
return "ROLL";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
56
102
|
/**
|
|
57
103
|
* Inline trend indicator: small arrow (green up / red down) plus the absolute
|
|
58
104
|
* delta in compact form. Returns `null` when there is no trend to display.
|
|
@@ -169,6 +215,8 @@ export const LogEntryHeader = memo(function ({
|
|
|
169
215
|
log.clientProjectFolder !== "";
|
|
170
216
|
const hasClientCwd =
|
|
171
217
|
log.clientCwd !== null && log.clientCwd !== undefined && log.clientCwd !== "";
|
|
218
|
+
const statusCue = STATUS_CUE_CLASSES[statusCategory];
|
|
219
|
+
const statusLabel = statusCueLabel(statusCategory, log.responseStatus);
|
|
172
220
|
|
|
173
221
|
return (
|
|
174
222
|
<TooltipProvider>
|
|
@@ -179,9 +227,11 @@ export const LogEntryHeader = memo(function ({
|
|
|
179
227
|
data-nav-id={`log-${log.id}`}
|
|
180
228
|
data-nav-action={expanded ? "collapse" : "expand"}
|
|
181
229
|
className={cn(
|
|
182
|
-
"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 px-3 py-
|
|
183
|
-
expanded
|
|
184
|
-
|
|
230
|
+
"group/header relative isolate flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 overflow-hidden px-3 py-2 cursor-pointer transition-[background-color,border-color,box-shadow]",
|
|
231
|
+
expanded
|
|
232
|
+
? "bg-[linear-gradient(90deg,#0f1720_0%,#090d12_50%,#07090d_100%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.06)]"
|
|
233
|
+
: "bg-[#080b10] hover:bg-[#0b1016]",
|
|
234
|
+
"select-none before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-px before:bg-white/10",
|
|
185
235
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
186
236
|
)}
|
|
187
237
|
onClick={onToggle}
|
|
@@ -192,13 +242,29 @@ export const LogEntryHeader = memo(function ({
|
|
|
192
242
|
}
|
|
193
243
|
}}
|
|
194
244
|
>
|
|
245
|
+
<span
|
|
246
|
+
className={cn(
|
|
247
|
+
"absolute left-0 top-2 bottom-2 w-0.5 rounded-r-full transition-colors",
|
|
248
|
+
statusCue.rail,
|
|
249
|
+
)}
|
|
250
|
+
aria-hidden="true"
|
|
251
|
+
/>
|
|
195
252
|
{/* Request ID */}
|
|
196
|
-
<span
|
|
197
|
-
|
|
253
|
+
<span
|
|
254
|
+
className={cn(
|
|
255
|
+
"inline-flex h-8 shrink-0 items-center gap-2 rounded-md border px-2 font-mono shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
|
|
256
|
+
statusCue.frame,
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
<span className={cn("size-1.5 rounded-full", statusCue.dot)} aria-hidden="true" />
|
|
260
|
+
<span className="text-[9px] font-semibold uppercase tracking-normal text-current/65">
|
|
261
|
+
Shot
|
|
262
|
+
</span>
|
|
263
|
+
<span className="text-xs font-semibold tabular-nums">#{log.id}</span>
|
|
198
264
|
</span>
|
|
199
265
|
|
|
200
266
|
{/* Request start time */}
|
|
201
|
-
<span className="flex items-center gap-1 text-muted-foreground text-xs shrink-0">
|
|
267
|
+
<span className="flex h-7 items-center gap-1 rounded-md border border-white/10 bg-black/20 px-2 text-muted-foreground text-xs shrink-0">
|
|
202
268
|
<Clock className="size-3" />
|
|
203
269
|
<span className="font-mono tabular-nums" title={log.timestamp}>
|
|
204
270
|
{formatTimestamp(log.timestamp, timeDisplayFormat)}
|
|
@@ -212,31 +278,31 @@ export const LogEntryHeader = memo(function ({
|
|
|
212
278
|
{/* Response Status — only shown for non-200 or pending. Each category
|
|
213
279
|
carries a distinct icon in addition to color so the meaning is
|
|
214
280
|
legible without color perception. */}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
281
|
+
<Badge
|
|
282
|
+
variant="outline"
|
|
283
|
+
className={cn(
|
|
284
|
+
"h-6 gap-1 rounded-md px-2 py-0 font-mono text-[10px] font-semibold uppercase tabular-nums shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]",
|
|
285
|
+
STATUS_BADGE_CLASSES[statusCategory],
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
{statusCategory === "success" ? (
|
|
289
|
+
<Check className="size-3" aria-label="Success" />
|
|
290
|
+
) : statusCategory === "server_error" ? (
|
|
291
|
+
<OctagonAlert className="size-3" aria-label="Server error" />
|
|
292
|
+
) : statusCategory === "client_error" ? (
|
|
293
|
+
<AlertTriangle className="size-3" aria-label="Client error" />
|
|
294
|
+
) : (
|
|
295
|
+
<Loader2 className="size-3 animate-spin" aria-label="Pending" />
|
|
296
|
+
)}
|
|
297
|
+
{statusLabel}
|
|
298
|
+
</Badge>
|
|
233
299
|
|
|
234
300
|
{warningCount > 0 && (
|
|
235
301
|
<Tooltip>
|
|
236
302
|
<TooltipTrigger asChild>
|
|
237
303
|
<Badge
|
|
238
304
|
variant="outline"
|
|
239
|
-
className="
|
|
305
|
+
className="h-6 gap-1 rounded-md border-amber-300/30 bg-amber-300/[0.10] px-2 py-0 font-mono text-[10px] tabular-nums text-amber-100"
|
|
240
306
|
>
|
|
241
307
|
<AlertTriangle className="size-3" aria-label="Tool schema warnings" />
|
|
242
308
|
{warningCount}
|
|
@@ -258,7 +324,12 @@ export const LogEntryHeader = memo(function ({
|
|
|
258
324
|
(isSlowResponse ? (
|
|
259
325
|
<Tooltip>
|
|
260
326
|
<TooltipTrigger asChild>
|
|
261
|
-
<span
|
|
327
|
+
<span
|
|
328
|
+
className={cn(
|
|
329
|
+
TELEMETRY_CHIP_CLASS,
|
|
330
|
+
"shrink-0 border-amber-300/25 text-amber-200",
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
262
333
|
<Clock className="size-3" />
|
|
263
334
|
<span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
|
|
264
335
|
<AlertTriangle className="size-3" aria-label="Slow response" />
|
|
@@ -270,7 +341,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
270
341
|
</TooltipContent>
|
|
271
342
|
</Tooltip>
|
|
272
343
|
) : (
|
|
273
|
-
<span className="
|
|
344
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
|
|
274
345
|
<Clock className="size-3" />
|
|
275
346
|
<span className="font-mono tabular-nums">{formatElapsed(log.elapsedMs)}</span>
|
|
276
347
|
</span>
|
|
@@ -280,7 +351,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
280
351
|
{log.streaming && firstChunkLabel !== null && (
|
|
281
352
|
<Tooltip>
|
|
282
353
|
<TooltipTrigger asChild>
|
|
283
|
-
<span className="
|
|
354
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-cyan-300")}>
|
|
284
355
|
<Radio className="size-3" />
|
|
285
356
|
<span className="font-mono tabular-nums">first {firstChunkLabel}</span>
|
|
286
357
|
</span>
|
|
@@ -291,7 +362,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
291
362
|
{log.streaming && tokenRateLabel !== null && (
|
|
292
363
|
<Tooltip>
|
|
293
364
|
<TooltipTrigger asChild>
|
|
294
|
-
<span className="
|
|
365
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-emerald-300")}>
|
|
295
366
|
<Gauge className="size-3" />
|
|
296
367
|
<span className="font-mono tabular-nums">{tokenRateLabel}</span>
|
|
297
368
|
</span>
|
|
@@ -302,7 +373,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
302
373
|
|
|
303
374
|
{/* Token counts */}
|
|
304
375
|
{hasTokens && (
|
|
305
|
-
<span className="
|
|
376
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "min-w-0 sm:shrink-0")}>
|
|
306
377
|
<Zap className="size-3 text-muted-foreground" />
|
|
307
378
|
<span className="flex min-w-0 flex-wrap items-center gap-x-1 font-mono tabular-nums">
|
|
308
379
|
<span
|
|
@@ -327,7 +398,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
327
398
|
)}
|
|
328
399
|
{/* Cache tokens */}
|
|
329
400
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
330
|
-
<span className="
|
|
401
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
|
|
331
402
|
<CacheTrendIndicator trend={cacheTrend?.creation ?? null} />
|
|
332
403
|
<span className="font-mono tabular-nums text-emerald-400">
|
|
333
404
|
KV Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
@@ -335,7 +406,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
335
406
|
</span>
|
|
336
407
|
)}
|
|
337
408
|
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
338
|
-
<span className="
|
|
409
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0")}>
|
|
339
410
|
<CacheTrendIndicator trend={cacheTrend?.read ?? null} />
|
|
340
411
|
<span className="font-mono tabular-nums text-purple-400">
|
|
341
412
|
KV Cache ~{formatTokens(log.cacheReadInputTokens)}
|
|
@@ -345,7 +416,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
345
416
|
|
|
346
417
|
{/* Message count */}
|
|
347
418
|
{messageCount !== null && (
|
|
348
|
-
<span className="
|
|
419
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
|
|
349
420
|
<MessageSquare className="size-3" />
|
|
350
421
|
<span className="font-mono tabular-nums">{messageCount}</span>
|
|
351
422
|
</span>
|
|
@@ -353,7 +424,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
353
424
|
|
|
354
425
|
{/* Tool count */}
|
|
355
426
|
{toolCount !== null && (
|
|
356
|
-
<span className="
|
|
427
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "shrink-0 text-muted-foreground")}>
|
|
357
428
|
<Wrench className="size-3" />
|
|
358
429
|
<span className="font-mono tabular-nums">{toolCount}</span>
|
|
359
430
|
</span>
|
|
@@ -362,7 +433,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
362
433
|
{/* Origin */}
|
|
363
434
|
{log.origin !== null && (
|
|
364
435
|
<span
|
|
365
|
-
className="hidden
|
|
436
|
+
className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-muted-foreground xl:flex")}
|
|
366
437
|
title={`Origin: ${log.origin}`}
|
|
367
438
|
>
|
|
368
439
|
<Globe className="size-3" />
|
|
@@ -376,7 +447,7 @@ export const LogEntryHeader = memo(function ({
|
|
|
376
447
|
{(hasClientPid || hasClientProjectFolder) && (
|
|
377
448
|
<Tooltip>
|
|
378
449
|
<TooltipTrigger asChild>
|
|
379
|
-
<span className="hidden
|
|
450
|
+
<span className={cn(TELEMETRY_CHIP_CLASS, "hidden shrink-0 text-purple-300 xl:flex")}>
|
|
380
451
|
<FileTerminal className="size-3" />
|
|
381
452
|
{hasClientProjectFolder ? (
|
|
382
453
|
<span className="font-mono tabular-nums">{log.clientProjectFolder}</span>
|
|
@@ -13,12 +13,11 @@ import {
|
|
|
13
13
|
Terminal,
|
|
14
14
|
Wrench,
|
|
15
15
|
} from "lucide-react";
|
|
16
|
-
import { memo, useMemo, useState, type JSX } from "react";
|
|
16
|
+
import { memo, Suspense, useMemo, useState, type JSX } from "react";
|
|
17
17
|
import { cn } from "../../lib/utils";
|
|
18
18
|
import { Badge } from "../ui/badge";
|
|
19
19
|
import { Button } from "../ui/button";
|
|
20
20
|
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
|
|
21
|
-
import { JsonViewer } from "../ui/json-viewer";
|
|
22
21
|
import { safeJsonValue } from "../ui/json-viewer-bulk";
|
|
23
22
|
import { ScrollArea } from "../ui/scroll-area";
|
|
24
23
|
import type {
|
|
@@ -26,6 +25,7 @@ import type {
|
|
|
26
25
|
RequestToolDefinition,
|
|
27
26
|
RequestToolsSummary,
|
|
28
27
|
} from "./requestTools";
|
|
28
|
+
import { LazyJsonViewer } from "./lazy";
|
|
29
29
|
import { useCopyFeedback } from "./useCopyFeedback";
|
|
30
30
|
|
|
31
31
|
type RequestToolsProps = {
|
|
@@ -152,17 +152,23 @@ function ToolRow({ tool, called }: { tool: RequestToolDefinition; called: boolea
|
|
|
152
152
|
</button>
|
|
153
153
|
</div>
|
|
154
154
|
<CollapsibleContent>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
{open && (
|
|
156
|
+
<div className="space-y-2 border-t border-white/10 bg-white/[0.02] px-2.5 py-2">
|
|
157
|
+
{tool.description !== null && (
|
|
158
|
+
<p className="text-xs leading-relaxed text-muted-foreground">{tool.description}</p>
|
|
159
|
+
)}
|
|
160
|
+
<RequiredParameters names={tool.requiredParameters} />
|
|
161
|
+
{tool.schema !== null && (
|
|
162
|
+
<ScrollArea className="max-h-64 rounded-md border border-white/10 bg-[#06080b] p-2">
|
|
163
|
+
<Suspense
|
|
164
|
+
fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}
|
|
165
|
+
>
|
|
166
|
+
<LazyJsonViewer data={safeJsonValue(tool.schema)} defaultExpandDepth={0} />
|
|
167
|
+
</Suspense>
|
|
168
|
+
</ScrollArea>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
166
172
|
</CollapsibleContent>
|
|
167
173
|
</div>
|
|
168
174
|
</Collapsible>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { memo, useState, useEffect, useMemo, type JSX } from "react";
|
|
1
|
+
import { memo, Suspense, useState, useEffect, useMemo, type JSX } from "react";
|
|
2
2
|
import { ChevronDown, ChevronRight, Loader2 } from "lucide-react";
|
|
3
3
|
import { Badge } from "../ui/badge";
|
|
4
|
-
import { JsonViewer } from "../ui/json-viewer";
|
|
5
4
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
6
5
|
import type { StreamingChunk } from "../../contracts";
|
|
6
|
+
import { LazyJsonViewer } from "./lazy";
|
|
7
7
|
|
|
8
8
|
export type StreamingChunkSequenceProps = {
|
|
9
9
|
logId: number;
|
|
@@ -140,7 +140,13 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
140
140
|
{chunk.type}
|
|
141
141
|
</span>
|
|
142
142
|
</div>
|
|
143
|
-
<
|
|
143
|
+
<Suspense
|
|
144
|
+
fallback={
|
|
145
|
+
<div className="text-xs text-muted-foreground">Loading JSON...</div>
|
|
146
|
+
}
|
|
147
|
+
>
|
|
148
|
+
<LazyJsonViewer data={chunk} defaultExpandDepth={0} showCopy />
|
|
149
|
+
</Suspense>
|
|
144
150
|
</div>
|
|
145
151
|
))}
|
|
146
152
|
</div>
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CSSProperties,
|
|
3
|
+
type JSX,
|
|
4
|
+
memo,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
2
11
|
import { AlertTriangle, ChevronRight, Clock, Zap } from "lucide-react";
|
|
3
12
|
import { isTurnBoundary } from "../../lib/stopReason";
|
|
4
13
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
@@ -9,7 +18,12 @@ import { ProviderLogo, detectProvider, type Provider } from "../providers/Provid
|
|
|
9
18
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
|
|
10
19
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
11
20
|
import { LogEntry } from "./LogEntry";
|
|
12
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
dispatchLogFocusRequest,
|
|
23
|
+
LOG_FOCUS_REQUEST_EVENT,
|
|
24
|
+
readLogFocusRequest,
|
|
25
|
+
type LogFocusRequest,
|
|
26
|
+
} from "./logFocus";
|
|
13
27
|
import { ThreadConnector } from "./ThreadConnector";
|
|
14
28
|
import { ToolTraceEvents } from "./ToolTraceEvents";
|
|
15
29
|
import { extractToolTraceEvents, isTurnCollapsible, type TurnEntry } from "./viewerState";
|
|
@@ -19,7 +33,11 @@ function formatElapsed(ms: number): string {
|
|
|
19
33
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
20
34
|
}
|
|
21
35
|
|
|
22
|
-
type
|
|
36
|
+
export type PendingTurnFocusRequest = LogFocusRequest & {
|
|
37
|
+
nonce: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type TurnGroupProps = {
|
|
23
41
|
entries: TurnEntry[];
|
|
24
42
|
viewMode: "simple" | "full";
|
|
25
43
|
strip: boolean;
|
|
@@ -29,6 +47,7 @@ type TurnGroupProps = {
|
|
|
29
47
|
comparisonPredecessors: Map<number, CapturedLog>;
|
|
30
48
|
turnIndex?: number;
|
|
31
49
|
timeDisplayFormat: TimeDisplayFormat;
|
|
50
|
+
pendingFocusRequest?: PendingTurnFocusRequest;
|
|
32
51
|
};
|
|
33
52
|
|
|
34
53
|
export const TurnGroup = memo(function TurnGroup({
|
|
@@ -41,6 +60,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
41
60
|
comparisonPredecessors,
|
|
42
61
|
turnIndex = 0,
|
|
43
62
|
timeDisplayFormat,
|
|
63
|
+
pendingFocusRequest,
|
|
44
64
|
}: TurnGroupProps): JSX.Element {
|
|
45
65
|
const lastIdx = entries.length - 1;
|
|
46
66
|
const lastStop = entries[lastIdx]?.stopReason ?? null;
|
|
@@ -75,6 +95,22 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
75
95
|
};
|
|
76
96
|
}, [collapsible, entries]);
|
|
77
97
|
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (pendingFocusRequest === undefined) return undefined;
|
|
100
|
+
if (!entries.some((entry) => entry.log.id === pendingFocusRequest.logId)) return undefined;
|
|
101
|
+
if (collapsible) setCollapsed(false);
|
|
102
|
+
|
|
103
|
+
const timer = window.setTimeout(() => {
|
|
104
|
+
dispatchLogFocusRequest({
|
|
105
|
+
logId: pendingFocusRequest.logId,
|
|
106
|
+
tab: pendingFocusRequest.tab,
|
|
107
|
+
source: "virtualizer",
|
|
108
|
+
});
|
|
109
|
+
}, 0);
|
|
110
|
+
|
|
111
|
+
return () => window.clearTimeout(timer);
|
|
112
|
+
}, [collapsible, entries, pendingFocusRequest]);
|
|
113
|
+
|
|
78
114
|
const toggleCollapse = useCallback(() => {
|
|
79
115
|
if (collapsible) setCollapsed((prev) => !prev);
|
|
80
116
|
}, [collapsible]);
|
|
@@ -120,7 +156,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
120
156
|
const StartCrab = useMemo(() => getCrabVariant(entries[0]?.log.id ?? 0), [entries]);
|
|
121
157
|
const EndCrab = useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
|
|
122
158
|
|
|
123
|
-
const bgClass = turnIndex % 2 === 0 ? "bg-
|
|
159
|
+
const bgClass = turnIndex % 2 === 0 ? "bg-[#070a0f]" : "bg-[#090d12]";
|
|
124
160
|
const aggregateIsSlow =
|
|
125
161
|
aggregate.maxElapsed !== null &&
|
|
126
162
|
slowResponseThresholdSeconds > 0 &&
|
|
@@ -154,17 +190,27 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
154
190
|
|
|
155
191
|
const firstLogId = entries[0]?.log.id ?? turnIndex;
|
|
156
192
|
const turnLabel = `Turn ${String(turnIndex + 1)}`;
|
|
193
|
+
const containmentStyle = useMemo<CSSProperties>(
|
|
194
|
+
() => ({
|
|
195
|
+
contentVisibility: "auto",
|
|
196
|
+
containIntrinsicSize: collapsed ? "48px" : "160px",
|
|
197
|
+
}),
|
|
198
|
+
[collapsed],
|
|
199
|
+
);
|
|
157
200
|
|
|
158
201
|
return (
|
|
159
202
|
<div
|
|
160
203
|
ref={containerRef}
|
|
204
|
+
style={containmentStyle}
|
|
161
205
|
tabIndex={collapsed ? undefined : 0}
|
|
162
206
|
role={collapsed ? undefined : "group"}
|
|
163
207
|
aria-label={collapsed ? undefined : turnLabel}
|
|
164
208
|
data-nav-id={collapsed ? undefined : `turn-${String(firstLogId)}`}
|
|
165
209
|
className={cn(
|
|
166
|
-
"border rounded-lg",
|
|
167
|
-
isPending
|
|
210
|
+
"border rounded-lg transition-[border-color,box-shadow]",
|
|
211
|
+
isPending
|
|
212
|
+
? "border-amber-300/15 shadow-[0_0_0_1px_rgba(252,211,77,0.04)]"
|
|
213
|
+
: "border-transparent",
|
|
168
214
|
!collapsed &&
|
|
169
215
|
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none",
|
|
170
216
|
)}
|
|
@@ -252,7 +298,7 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
252
298
|
{entries.length > 1 && (
|
|
253
299
|
<div
|
|
254
300
|
className={cn(
|
|
255
|
-
"flex-1 min-w-0 mb-0.5 rounded-lg border border-
|
|
301
|
+
"flex-1 min-w-0 mb-0.5 rounded-lg border border-cyan-300/10 py-1.5 px-3 flex items-center gap-3 text-xs shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]",
|
|
256
302
|
bgClass,
|
|
257
303
|
)}
|
|
258
304
|
>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
2
|
+
import { type JSX, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import type { CapturedLog } from "../../contracts";
|
|
4
|
+
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
5
|
+
import type { CacheTrendEntry } from "./cacheTrend";
|
|
6
|
+
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest } from "./logFocus";
|
|
7
|
+
import { TurnGroup } from "./TurnGroup";
|
|
8
|
+
import type { PendingTurnFocusRequest } from "./TurnGroup";
|
|
9
|
+
import type { TurnGroupData } from "./viewerState";
|
|
10
|
+
|
|
11
|
+
const VIRTUALIZE_TURN_THRESHOLD = 24;
|
|
12
|
+
const ESTIMATED_TURN_HEIGHT = 132;
|
|
13
|
+
const VIRTUAL_OVERSCAN = 8;
|
|
14
|
+
|
|
15
|
+
type TurnGroupListProps = {
|
|
16
|
+
turnGroups: TurnGroupData[];
|
|
17
|
+
viewMode: "simple" | "full";
|
|
18
|
+
strip: boolean;
|
|
19
|
+
slowResponseThresholdSeconds: number;
|
|
20
|
+
cacheTrends?: Map<number, CacheTrendEntry>;
|
|
21
|
+
onCompareWithPrevious: (log: CapturedLog) => void;
|
|
22
|
+
comparisonPredecessors: Map<number, CapturedLog>;
|
|
23
|
+
timeDisplayFormat: TimeDisplayFormat;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type PendingVirtualFocusRequest = PendingTurnFocusRequest & {
|
|
27
|
+
turnIndex: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function shouldVirtualizeTurnGroups(turnCount: number): boolean {
|
|
31
|
+
return turnCount > VIRTUALIZE_TURN_THRESHOLD;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function findTurnGroupIndexForLog(
|
|
35
|
+
turnGroups: readonly TurnGroupData[],
|
|
36
|
+
logId: number,
|
|
37
|
+
): number | null {
|
|
38
|
+
for (let index = 0; index < turnGroups.length; index++) {
|
|
39
|
+
const group = turnGroups[index];
|
|
40
|
+
if (group === undefined) continue;
|
|
41
|
+
if (group.entries.some((entry) => entry.log.id === logId)) return index;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildLogTurnIndex(turnGroups: readonly TurnGroupData[]): Map<number, number> {
|
|
47
|
+
const result = new Map<number, number>();
|
|
48
|
+
for (let index = 0; index < turnGroups.length; index++) {
|
|
49
|
+
const group = turnGroups[index];
|
|
50
|
+
if (group === undefined) continue;
|
|
51
|
+
for (const entry of group.entries) {
|
|
52
|
+
result.set(entry.log.id, index);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderTurnGroup(
|
|
59
|
+
turnGroup: TurnGroupData,
|
|
60
|
+
props: TurnGroupListProps,
|
|
61
|
+
pendingFocusRequest?: PendingTurnFocusRequest,
|
|
62
|
+
): JSX.Element {
|
|
63
|
+
return (
|
|
64
|
+
<TurnGroup
|
|
65
|
+
key={turnGroup.turnIndex}
|
|
66
|
+
entries={turnGroup.entries}
|
|
67
|
+
viewMode={props.viewMode}
|
|
68
|
+
strip={props.strip}
|
|
69
|
+
slowResponseThresholdSeconds={props.slowResponseThresholdSeconds}
|
|
70
|
+
timeDisplayFormat={props.timeDisplayFormat}
|
|
71
|
+
cacheTrends={props.cacheTrends}
|
|
72
|
+
onCompareWithPrevious={props.onCompareWithPrevious}
|
|
73
|
+
comparisonPredecessors={props.comparisonPredecessors}
|
|
74
|
+
turnIndex={turnGroup.turnIndex}
|
|
75
|
+
pendingFocusRequest={pendingFocusRequest}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function TurnGroupList(props: TurnGroupListProps): JSX.Element {
|
|
81
|
+
if (!shouldVirtualizeTurnGroups(props.turnGroups.length)) {
|
|
82
|
+
return <>{props.turnGroups.map((turnGroup) => renderTurnGroup(turnGroup, props))}</>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return <VirtualizedTurnGroupList {...props} />;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function VirtualizedTurnGroupList(props: TurnGroupListProps): JSX.Element {
|
|
89
|
+
const { turnGroups } = props;
|
|
90
|
+
const [pendingFocusRequest, setPendingFocusRequest] = useState<PendingVirtualFocusRequest | null>(
|
|
91
|
+
null,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const virtualizer = useWindowVirtualizer({
|
|
95
|
+
count: turnGroups.length,
|
|
96
|
+
estimateSize: () => ESTIMATED_TURN_HEIGHT,
|
|
97
|
+
overscan: VIRTUAL_OVERSCAN,
|
|
98
|
+
getItemKey: (index) => turnGroups[index]?.turnIndex ?? index,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const turnIndexByLogId = useMemo(() => buildLogTurnIndex(turnGroups), [turnGroups]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const handleLogFocusRequest = (event: Event): void => {
|
|
105
|
+
const request = readLogFocusRequest(event);
|
|
106
|
+
if (request === null) return;
|
|
107
|
+
if (request.source === "virtualizer") return;
|
|
108
|
+
|
|
109
|
+
const turnIndex = turnIndexByLogId.get(request.logId);
|
|
110
|
+
if (turnIndex === undefined) return;
|
|
111
|
+
virtualizer.scrollToIndex(turnIndex, { align: "center" });
|
|
112
|
+
setPendingFocusRequest((previous) => ({
|
|
113
|
+
...request,
|
|
114
|
+
turnIndex,
|
|
115
|
+
nonce: previous === null ? 1 : previous.nonce + 1,
|
|
116
|
+
}));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
window.addEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
120
|
+
return () => {
|
|
121
|
+
window.removeEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
122
|
+
};
|
|
123
|
+
}, [turnIndexByLogId, virtualizer]);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
className="relative w-full"
|
|
128
|
+
style={{ height: `${virtualizer.getTotalSize()}px` }}
|
|
129
|
+
data-virtualized-turn-list="true"
|
|
130
|
+
>
|
|
131
|
+
{virtualizer.getVirtualItems().map((virtualItem) => {
|
|
132
|
+
const turnGroup = turnGroups[virtualItem.index];
|
|
133
|
+
if (turnGroup === undefined) return null;
|
|
134
|
+
const itemFocusRequest =
|
|
135
|
+
pendingFocusRequest !== null && pendingFocusRequest.turnIndex === virtualItem.index
|
|
136
|
+
? pendingFocusRequest
|
|
137
|
+
: undefined;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
key={virtualItem.key}
|
|
142
|
+
data-index={virtualItem.index}
|
|
143
|
+
ref={virtualizer.measureElement}
|
|
144
|
+
className="absolute left-0 top-0 w-full"
|
|
145
|
+
style={{ transform: `translateY(${virtualItem.start}px)` }}
|
|
146
|
+
>
|
|
147
|
+
{renderTurnGroup(turnGroup, props, itemFocusRequest)}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|