@tonyclaw/agent-inspector 2.1.16 → 2.1.17
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 +57 -13
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-Ccrrcx1j.js → CompareDrawer-B9sLBHw5.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
- package/.output/public/assets/{ReplayDialog-C7axhr-l.js → ReplayDialog-D9I9W9n3.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-WOptg8j_.js → RequestAnatomy-DGXK_Rii.js} +1 -1
- package/.output/public/assets/{ResponseView-CSAcxh_M.js → ResponseView-df_JzwsS.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-DJS5KhPx.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
- package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
- package/.output/public/assets/index-Blqvndcn.js +1 -0
- package/.output/public/assets/index-Cs79WRQj.css +1 -0
- package/.output/public/assets/{index-DMPNh46t.js → index-Dqc2r1ea.js} +1 -1
- package/.output/public/assets/{json-viewer-inX5QSa3.js → json-viewer-D3QWQlSB.js} +1 -1
- package/.output/public/assets/{main-aCAKYGSD.js → main-ZhxhOCmF.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +220 -202
- package/.output/server/{_sessionId-C0fhvQnn.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-CO-Ti5sg.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-N4J7uBCX.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +1858 -1396
- package/.output/server/_ssr/{ReplayDialog-J1VxC0In.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-DBH-F3rq.mjs → RequestAnatomy-HQfrxjO1.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-DLFwV7Hd.mjs → ResponseView-QSlSD11e.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-BywmuND0.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +2 -2
- package/.output/server/_ssr/{index-CStymIc0.mjs → index-D_EzfXaN.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-DlQu0N1x.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
- package/.output/server/_ssr/{router-CQ4Zxhmr.mjs → router-__o2wrfO.mjs} +398 -95
- package/.output/server/{_tanstack-start-manifest_v-BHeJBGfo.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
- package/.output/server/index.mjs +73 -73
- package/README.md +57 -2
- package/package.json +1 -1
- package/src/cli/startupOutput.ts +16 -0
- package/src/cli.ts +61 -15
- package/src/components/ProxyViewer.tsx +395 -64
- package/src/components/providers/ProviderCard.tsx +132 -31
- package/src/components/providers/ProviderForm.tsx +43 -11
- package/src/components/providers/ProvidersPanel.tsx +1 -1
- package/src/components/proxy-viewer/ConversationHeader.tsx +133 -112
- package/src/components/proxy-viewer/LogEntry.tsx +109 -26
- package/src/components/proxy-viewer/LogEntryHeader.tsx +339 -329
- package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
- package/src/components/proxy-viewer/TurnGroup.tsx +22 -63
- package/src/components/proxy-viewer/bodyHydration.ts +56 -0
- package/src/lib/providerTestContract.ts +56 -10
- package/src/lib/providerTestPrompt.ts +23 -1
- package/src/lib/upstreamUrl.ts +105 -1
- package/src/mcp/toolHandlers.ts +1 -1
- package/src/routes/api/providers.$providerId.test.log.ts +85 -10
- package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +0 -106
- package/.output/public/assets/_sessionId-DUSalzKH.js +0 -1
- package/.output/public/assets/index-BPpA21dY.css +0 -1
- package/.output/public/assets/index-Bt0Az2I2.js +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { cn } from "../../lib/utils";
|
|
1
|
+
import type { JSX, KeyboardEvent, MouseEvent } from "react";
|
|
3
2
|
import { isTurnBoundary, type StopReason } from "../../lib/stopReason";
|
|
4
|
-
import {
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
5
4
|
|
|
6
5
|
export type ThreadConnectorProps = {
|
|
7
6
|
stopReason: StopReason;
|
|
@@ -11,13 +10,13 @@ export type ThreadConnectorProps = {
|
|
|
11
10
|
isTurnStart: boolean;
|
|
12
11
|
/** True when this entry is the turn's only request. */
|
|
13
12
|
isOnlyEntry?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
crabIndex?: number;
|
|
16
|
-
/** When true the crab is clickable (collapse / expand a turn). */
|
|
13
|
+
/** When true the timeline marker is clickable (collapse / expand a turn). */
|
|
17
14
|
collapsible?: boolean;
|
|
18
15
|
onToggle?: () => void;
|
|
19
16
|
};
|
|
20
17
|
|
|
18
|
+
type MarkerTone = "start" | "end" | "fused" | "running" | "idle";
|
|
19
|
+
|
|
21
20
|
function boundaryTitle(stopReason: StopReason): string {
|
|
22
21
|
switch (stopReason) {
|
|
23
22
|
case "end_turn":
|
|
@@ -32,104 +31,124 @@ function boundaryTitle(stopReason: StopReason): string {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
function markerTone({
|
|
35
|
+
isFusedBoundary,
|
|
36
|
+
isBoundary,
|
|
37
|
+
isTurnStart,
|
|
38
|
+
isRunning,
|
|
39
|
+
}: {
|
|
40
|
+
isFusedBoundary: boolean;
|
|
41
|
+
isBoundary: boolean;
|
|
42
|
+
isTurnStart: boolean;
|
|
43
|
+
isRunning: boolean;
|
|
44
|
+
}): MarkerTone {
|
|
45
|
+
if (isFusedBoundary) return "fused";
|
|
46
|
+
if (isBoundary) return "end";
|
|
47
|
+
if (isTurnStart) return "start";
|
|
48
|
+
if (isRunning) return "running";
|
|
49
|
+
return "idle";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function markerClass(tone: MarkerTone, interactive: boolean): string {
|
|
53
|
+
const base =
|
|
54
|
+
"inline-flex size-3.5 items-center justify-center rounded-full border transition-colors";
|
|
55
|
+
const cursor = interactive ? "cursor-pointer hover:border-cyan-200/70 hover:bg-cyan-300/20" : "";
|
|
56
|
+
|
|
57
|
+
switch (tone) {
|
|
58
|
+
case "start":
|
|
59
|
+
return cn(base, cursor, "border-cyan-300/50 bg-cyan-300/12 text-cyan-200");
|
|
60
|
+
case "end":
|
|
61
|
+
return cn(base, cursor, "border-amber-300/55 bg-amber-300/12 text-amber-200");
|
|
62
|
+
case "fused":
|
|
63
|
+
return cn(
|
|
64
|
+
base,
|
|
65
|
+
cursor,
|
|
66
|
+
"border-slate-200/50 bg-slate-200/10 text-slate-100 ring-2 ring-cyan-300/15",
|
|
67
|
+
);
|
|
68
|
+
case "running":
|
|
69
|
+
return cn(
|
|
70
|
+
base,
|
|
71
|
+
cursor,
|
|
72
|
+
"border-amber-300/45 bg-amber-300/10 text-amber-200 ring-2 ring-amber-300/10",
|
|
73
|
+
);
|
|
74
|
+
case "idle":
|
|
75
|
+
return cn(base, cursor, "border-muted-foreground/25 bg-muted/30 text-muted-foreground/60");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function markerInnerClass(tone: MarkerTone): string {
|
|
80
|
+
switch (tone) {
|
|
81
|
+
case "running":
|
|
82
|
+
return "size-1.5 rounded-full bg-current animate-pulse";
|
|
83
|
+
case "idle":
|
|
84
|
+
return "size-1 rounded-full bg-current opacity-55";
|
|
85
|
+
case "start":
|
|
86
|
+
case "end":
|
|
87
|
+
case "fused":
|
|
88
|
+
return "size-1.5 rounded-full bg-current";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function markerTitle(tone: MarkerTone, stopReason: StopReason): string {
|
|
93
|
+
switch (tone) {
|
|
94
|
+
case "start":
|
|
95
|
+
return "Start of turn";
|
|
96
|
+
case "end":
|
|
97
|
+
return boundaryTitle(stopReason);
|
|
98
|
+
case "fused":
|
|
99
|
+
return "Single-request turn";
|
|
100
|
+
case "running":
|
|
101
|
+
return "Processing";
|
|
102
|
+
case "idle":
|
|
103
|
+
return "Request in turn";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
42
107
|
export function ThreadConnector({
|
|
43
108
|
stopReason,
|
|
44
109
|
isPending,
|
|
45
110
|
isFirst,
|
|
46
111
|
isTurnStart,
|
|
47
112
|
isOnlyEntry = false,
|
|
48
|
-
crabIndex = 0,
|
|
49
113
|
collapsible = false,
|
|
50
114
|
onToggle,
|
|
51
115
|
}: ThreadConnectorProps): JSX.Element {
|
|
52
116
|
const isBoundary = isTurnBoundary(stopReason);
|
|
53
117
|
const isFusedBoundary = isOnlyEntry && isTurnStart && isBoundary;
|
|
54
118
|
const isRunning = isPending && !isBoundary;
|
|
55
|
-
const
|
|
56
|
-
const
|
|
119
|
+
const tone = markerTone({ isFusedBoundary, isBoundary, isTurnStart, isRunning });
|
|
120
|
+
const interactive = collapsible && onToggle !== undefined;
|
|
121
|
+
|
|
122
|
+
const handleMarkerClick = (event: MouseEvent<HTMLSpanElement>): void => {
|
|
123
|
+
if (!interactive || onToggle === undefined) return;
|
|
124
|
+
event.stopPropagation();
|
|
125
|
+
onToggle();
|
|
126
|
+
};
|
|
57
127
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
onClick: (e: React.MouseEvent) => {
|
|
65
|
-
e.stopPropagation();
|
|
66
|
-
onToggle();
|
|
67
|
-
},
|
|
68
|
-
onKeyDown: (e: React.KeyboardEvent) => {
|
|
69
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
onToggle();
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
} as const)
|
|
75
|
-
: ({} as const);
|
|
128
|
+
const handleMarkerKeyDown = (event: KeyboardEvent<HTMLSpanElement>): void => {
|
|
129
|
+
if (!interactive || onToggle === undefined) return;
|
|
130
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
onToggle();
|
|
133
|
+
};
|
|
76
134
|
|
|
77
135
|
return (
|
|
78
136
|
<div className="flex flex-col items-center w-6 shrink-0 pt-0.5 pb-0.5">
|
|
79
|
-
{/* Top spacer — crab centre must land on the LogEntry index midline.
|
|
80
|
-
* Index midline from container top = border(1px) + py-1(0.25rem) + ½line(0.5rem)
|
|
81
|
-
* = 1px + 0.75rem. Crab centre = pt(2px) + spacer + 7px(half crab).
|
|
82
|
-
* ∴ spacer = 0.75rem - 8px. (at 16px root: 12-8=4px; 2+4+7=13px). */}
|
|
83
137
|
<div className="flex justify-center h-[calc(0.75rem-8px)]">
|
|
84
138
|
{!isFirst && <div className="w-0.5 bg-muted-foreground/30 h-full" />}
|
|
85
139
|
</div>
|
|
86
140
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)}
|
|
98
|
-
/>
|
|
99
|
-
</span>
|
|
100
|
-
) : isBoundary ? (
|
|
101
|
-
<span title={boundaryTitle(stopReason)} {...interactiveProps}>
|
|
102
|
-
<Crab
|
|
103
|
-
className={cn(
|
|
104
|
-
"size-3.5 text-amber-400",
|
|
105
|
-
"animate-crab-settle",
|
|
106
|
-
"drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]",
|
|
107
|
-
)}
|
|
108
|
-
/>
|
|
109
|
-
</span>
|
|
110
|
-
) : isTurnStart ? (
|
|
111
|
-
<span title="Start of turn" {...interactiveProps}>
|
|
112
|
-
<Crab
|
|
113
|
-
className={cn(
|
|
114
|
-
"size-3.5 text-cyan-300/85",
|
|
115
|
-
"animate-crab-appear",
|
|
116
|
-
"drop-shadow-[0_0_4px_rgba(103,232,249,0.32)]",
|
|
117
|
-
)}
|
|
118
|
-
/>
|
|
119
|
-
</span>
|
|
120
|
-
) : isRunning ? (
|
|
121
|
-
<span title="Processing…">
|
|
122
|
-
<Crab className={cn("size-3.5 text-amber-300/80", "animate-crab-crawl")} />
|
|
123
|
-
</span>
|
|
124
|
-
) : (
|
|
125
|
-
<span>
|
|
126
|
-
<Crab className="size-3.5 text-muted-foreground/40" />
|
|
127
|
-
</span>
|
|
128
|
-
)}
|
|
141
|
+
<span
|
|
142
|
+
role={interactive ? "button" : undefined}
|
|
143
|
+
tabIndex={interactive ? 0 : undefined}
|
|
144
|
+
title={markerTitle(tone, stopReason)}
|
|
145
|
+
className={markerClass(tone, interactive)}
|
|
146
|
+
onClick={handleMarkerClick}
|
|
147
|
+
onKeyDown={handleMarkerKeyDown}
|
|
148
|
+
>
|
|
149
|
+
<span className={markerInnerClass(tone)} />
|
|
150
|
+
</span>
|
|
129
151
|
|
|
130
|
-
{/* Bottom spacer — flex-1 fills whatever height the LogEntry
|
|
131
|
-
* consumes (header + expanded content). Turn boundaries have
|
|
132
|
-
* no outgoing line. */}
|
|
133
152
|
<div className="flex-1 flex justify-center min-h-0">
|
|
134
153
|
{!isBoundary && (
|
|
135
154
|
<div
|
|
@@ -13,9 +13,7 @@ import { isTurnBoundary } from "../../lib/stopReason";
|
|
|
13
13
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
14
14
|
import { cn, formatTokens } from "../../lib/utils";
|
|
15
15
|
import type { CapturedLog } from "../../contracts";
|
|
16
|
-
import { getCrabVariant } from "../ui/crab-variants";
|
|
17
16
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
|
|
18
|
-
import { ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
|
|
19
17
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
20
18
|
import { LogEntry } from "./LogEntry";
|
|
21
19
|
import {
|
|
@@ -145,10 +143,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
145
143
|
};
|
|
146
144
|
}, [entries, lastIdx]);
|
|
147
145
|
|
|
148
|
-
// Crab variant creators for the dual-crab collapsed layout
|
|
149
|
-
const StartCrab = useMemo(() => getCrabVariant(entries[0]?.log.id ?? 0), [entries]);
|
|
150
|
-
const EndCrab = useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
|
|
151
|
-
|
|
152
146
|
const bgClass = turnIndex % 2 === 0 ? "bg-white/[0.012]" : "bg-cyan-300/[0.012]";
|
|
153
147
|
const aggregateIsSlow =
|
|
154
148
|
aggregate.maxElapsed !== null &&
|
|
@@ -209,7 +203,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
209
203
|
)}
|
|
210
204
|
>
|
|
211
205
|
{collapsed ? (
|
|
212
|
-
/* ---- Collapsed: dual-crab (+ summary card for multi-log turns) ---- */
|
|
213
206
|
<div
|
|
214
207
|
data-nav-id={`turn-collapsed-${String(firstLogId)}`}
|
|
215
208
|
data-nav-action="expand"
|
|
@@ -231,78 +224,38 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
231
224
|
</span>
|
|
232
225
|
</div>
|
|
233
226
|
|
|
234
|
-
|
|
235
|
-
<div className="w-6 shrink-0 flex flex-col items-center pt-0.5 pb-0.5">
|
|
227
|
+
<div className="w-6 shrink-0 flex flex-col items-center pt-1.5 pb-1.5">
|
|
236
228
|
<div className="flex justify-center h-[calc(0.75rem-8px)]" />
|
|
237
|
-
<span
|
|
238
|
-
role="button"
|
|
239
|
-
tabIndex={0}
|
|
240
|
-
title="Start of turn - click to expand"
|
|
241
|
-
className="cursor-pointer"
|
|
242
|
-
onClick={(e) => {
|
|
243
|
-
e.stopPropagation();
|
|
244
|
-
toggleCollapse();
|
|
245
|
-
}}
|
|
246
|
-
onKeyDown={(e) => {
|
|
247
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
248
|
-
e.preventDefault();
|
|
249
|
-
toggleCollapse();
|
|
250
|
-
}
|
|
251
|
-
}}
|
|
252
|
-
>
|
|
253
|
-
<StartCrab
|
|
254
|
-
className={cn(
|
|
255
|
-
"size-3.5 text-cyan-300/85",
|
|
256
|
-
"animate-crab-appear drop-shadow-[0_0_4px_rgba(103,232,249,0.32)]",
|
|
257
|
-
)}
|
|
258
|
-
/>
|
|
259
|
-
</span>
|
|
260
|
-
|
|
229
|
+
<span className="size-2 rounded-full border border-cyan-300/45 bg-cyan-300/20" />
|
|
261
230
|
<div className="flex-1 flex justify-center min-h-0">
|
|
262
|
-
<div className="w-
|
|
231
|
+
<div className="w-px bg-muted-foreground/25 h-full" />
|
|
263
232
|
</div>
|
|
264
|
-
|
|
265
233
|
<span
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}}
|
|
274
|
-
onKeyDown={(e) => {
|
|
275
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
276
|
-
e.preventDefault();
|
|
277
|
-
toggleCollapse();
|
|
278
|
-
}
|
|
279
|
-
}}
|
|
280
|
-
>
|
|
281
|
-
<EndCrab
|
|
282
|
-
className={cn(
|
|
283
|
-
"size-3.5 text-amber-400",
|
|
284
|
-
"animate-crab-settle drop-shadow-[0_0_4px_rgba(251,191,36,0.5)]",
|
|
285
|
-
)}
|
|
286
|
-
/>
|
|
287
|
-
</span>
|
|
234
|
+
className={cn(
|
|
235
|
+
"size-2 rounded-full border",
|
|
236
|
+
isComplete
|
|
237
|
+
? "border-amber-300/45 bg-amber-300/20"
|
|
238
|
+
: "border-muted-foreground/30 bg-muted/30",
|
|
239
|
+
)}
|
|
240
|
+
/>
|
|
288
241
|
</div>
|
|
289
242
|
|
|
290
243
|
{/* Summary content - hidden for single-log turns. */}
|
|
291
244
|
{entries.length > 1 && (
|
|
292
245
|
<div
|
|
293
246
|
className={cn(
|
|
294
|
-
"flex-1 min-w-0 mb-0.5 rounded-
|
|
295
|
-
"border border-border bg-card shadow-sm",
|
|
247
|
+
"flex-1 min-w-0 mb-0.5 rounded-[8px] py-1.5 px-3 flex items-center gap-3 text-xs shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]",
|
|
248
|
+
"border border-border bg-card shadow-sm transition-colors hover:border-cyan-200/20",
|
|
296
249
|
bgClass,
|
|
297
250
|
)}
|
|
298
251
|
>
|
|
299
|
-
<span className=
|
|
300
|
-
|
|
252
|
+
<span className="font-mono text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/70">
|
|
253
|
+
{turnLabel}
|
|
301
254
|
</span>
|
|
302
255
|
|
|
303
256
|
{/* ID range */}
|
|
304
257
|
<span
|
|
305
|
-
className="text-blue-
|
|
258
|
+
className="text-blue-300/85 font-mono font-semibold tabular-nums shrink-0"
|
|
306
259
|
title={`Log IDs ${String(entries[0]?.log.id ?? "?")} ~ ${String(
|
|
307
260
|
entries[lastIdx]?.log.id ?? "?",
|
|
308
261
|
)}`}
|
|
@@ -363,6 +316,13 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
363
316
|
|
|
364
317
|
{/* Spacer */}
|
|
365
318
|
<span className="flex-1 min-w-0" />
|
|
319
|
+
|
|
320
|
+
<span
|
|
321
|
+
className="border border-input bg-background inline-flex size-7 shrink-0 items-center justify-center rounded-md text-slate-200"
|
|
322
|
+
aria-hidden="true"
|
|
323
|
+
>
|
|
324
|
+
<ChevronRight className="size-4 shrink-0" />
|
|
325
|
+
</span>
|
|
366
326
|
</div>
|
|
367
327
|
)}
|
|
368
328
|
</div>
|
|
@@ -389,7 +349,6 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
389
349
|
isFirst={visibleIdx === 0}
|
|
390
350
|
isTurnStart={isTurnStart}
|
|
391
351
|
isOnlyEntry={isSingleLog}
|
|
392
|
-
crabIndex={log.id % 12}
|
|
393
352
|
collapsible={collapsible && isTurnStart}
|
|
394
353
|
onToggle={toggleCollapse}
|
|
395
354
|
/>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ApiTimeoutError } from "../../lib/apiClient";
|
|
2
|
+
|
|
3
|
+
export type FullBodyHydrationError = {
|
|
4
|
+
title: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const DEFAULT_FULL_BODY_HYDRATION_ERROR_DETAIL =
|
|
9
|
+
"The compact log metadata is still available. Retry if the local server was restarting or the body store was briefly unavailable.";
|
|
10
|
+
|
|
11
|
+
export function formatBytes(bytes: number): string {
|
|
12
|
+
if (bytes < 1024) return `${bytes.toLocaleString()} B`;
|
|
13
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
14
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
|
|
15
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatBodyHydrationFootprint(
|
|
19
|
+
requestBytes: number | null | undefined,
|
|
20
|
+
responseBytes: number | null | undefined,
|
|
21
|
+
): string | null {
|
|
22
|
+
const parts: string[] = [];
|
|
23
|
+
|
|
24
|
+
if (requestBytes !== null && requestBytes !== undefined) {
|
|
25
|
+
parts.push(`Request ${formatBytes(requestBytes)}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (responseBytes !== null && responseBytes !== undefined) {
|
|
29
|
+
parts.push(`Response ${formatBytes(responseBytes)}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return parts.length === 0 ? null : parts.join(" · ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function describeFullBodyHydrationError(error: unknown): FullBodyHydrationError {
|
|
36
|
+
if (error instanceof ApiTimeoutError) {
|
|
37
|
+
return {
|
|
38
|
+
title: "Full bodies timed out",
|
|
39
|
+
detail:
|
|
40
|
+
"The log store did not answer before the UI timeout. Compact metadata is still available; retry loading the bodies.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (error instanceof Error) {
|
|
45
|
+
const message = error.message.trim();
|
|
46
|
+
return {
|
|
47
|
+
title: "Full bodies could not be loaded",
|
|
48
|
+
detail: message === "" ? DEFAULT_FULL_BODY_HYDRATION_ERROR_DETAIL : message,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
title: "Full bodies could not be loaded",
|
|
54
|
+
detail: DEFAULT_FULL_BODY_HYDRATION_ERROR_DETAIL,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -72,22 +72,58 @@ const ProviderFormatTestResultsSchema = z.object({
|
|
|
72
72
|
streaming: ProviderTestStateSchema,
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
anthropic: ProviderFormatTestResultsSchema,
|
|
77
|
-
openai: ProviderFormatTestResultsSchema,
|
|
78
|
-
});
|
|
75
|
+
export type ProviderFormatTestResults = z.infer<typeof ProviderFormatTestResultsSchema>;
|
|
79
76
|
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
77
|
+
export function createNotConfiguredProviderFormatTestResults(): ProviderFormatTestResults {
|
|
78
|
+
return {
|
|
79
|
+
nonStreaming: { notConfigured: true },
|
|
80
|
+
streaming: { notConfigured: true },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const ModelTestResultsSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
anthropic: ProviderFormatTestResultsSchema,
|
|
87
|
+
openai: ProviderFormatTestResultsSchema.optional(),
|
|
88
|
+
openaiChat: ProviderFormatTestResultsSchema.optional(),
|
|
89
|
+
openaiResponses: ProviderFormatTestResultsSchema.optional(),
|
|
90
|
+
})
|
|
91
|
+
.transform((value) => {
|
|
92
|
+
const openaiChat =
|
|
93
|
+
value.openaiChat ?? value.openai ?? createNotConfiguredProviderFormatTestResults();
|
|
94
|
+
return {
|
|
95
|
+
...value,
|
|
96
|
+
openai: value.openai ?? openaiChat,
|
|
97
|
+
openaiChat,
|
|
98
|
+
openaiResponses: value.openaiResponses ?? createNotConfiguredProviderFormatTestResults(),
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const ProviderTestResultsSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
anthropic: ProviderFormatTestResultsSchema,
|
|
105
|
+
openai: ProviderFormatTestResultsSchema.optional(),
|
|
106
|
+
openaiChat: ProviderFormatTestResultsSchema.optional(),
|
|
107
|
+
openaiResponses: ProviderFormatTestResultsSchema.optional(),
|
|
108
|
+
models: z.record(z.string(), ModelTestResultsSchema).optional(),
|
|
109
|
+
testedAt: z.string().optional(),
|
|
110
|
+
})
|
|
111
|
+
.transform((value) => {
|
|
112
|
+
const openaiChat =
|
|
113
|
+
value.openaiChat ?? value.openai ?? createNotConfiguredProviderFormatTestResults();
|
|
114
|
+
return {
|
|
115
|
+
...value,
|
|
116
|
+
openai: value.openai ?? openaiChat,
|
|
117
|
+
openaiChat,
|
|
118
|
+
openaiResponses: value.openaiResponses ?? createNotConfiguredProviderFormatTestResults(),
|
|
119
|
+
};
|
|
120
|
+
});
|
|
86
121
|
|
|
87
122
|
export type ProviderTestErrorType = z.infer<typeof ProviderTestErrorTypeSchema>;
|
|
88
123
|
export type ProviderTestDebug = z.infer<typeof ProviderTestDebugSchema>;
|
|
89
124
|
export type ProviderTestResult = z.infer<typeof ProviderTestResultSchema>;
|
|
90
125
|
export type ProviderTestState = z.infer<typeof ProviderTestStateSchema>;
|
|
126
|
+
export type ProviderModelTestResults = z.infer<typeof ModelTestResultsSchema>;
|
|
91
127
|
export type ProviderTestResults = z.infer<typeof ProviderTestResultsSchema>;
|
|
92
128
|
|
|
93
129
|
export function createPendingProviderTestResults(): ProviderTestResults {
|
|
@@ -100,6 +136,14 @@ export function createPendingProviderTestResults(): ProviderTestResults {
|
|
|
100
136
|
nonStreaming: { testing: true },
|
|
101
137
|
streaming: { testing: true },
|
|
102
138
|
},
|
|
139
|
+
openaiChat: {
|
|
140
|
+
nonStreaming: { testing: true },
|
|
141
|
+
streaming: { testing: true },
|
|
142
|
+
},
|
|
143
|
+
openaiResponses: {
|
|
144
|
+
nonStreaming: { testing: true },
|
|
145
|
+
streaming: { testing: true },
|
|
146
|
+
},
|
|
103
147
|
};
|
|
104
148
|
}
|
|
105
149
|
|
|
@@ -118,5 +162,7 @@ export function createFailedProviderTestResults(
|
|
|
118
162
|
return {
|
|
119
163
|
anthropic: createFormatResult(),
|
|
120
164
|
openai: createFormatResult(),
|
|
165
|
+
openaiChat: createFormatResult(),
|
|
166
|
+
openaiResponses: createFormatResult(),
|
|
121
167
|
};
|
|
122
168
|
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
export type ProviderTestMode = "non-streaming" | "streaming";
|
|
2
|
+
export type ProviderTestApiShape = "chat" | "responses";
|
|
2
3
|
|
|
3
4
|
export type ProviderTestMessage = {
|
|
4
5
|
role: "user";
|
|
5
6
|
content: string;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type ProviderTestChatRequestBody = {
|
|
9
10
|
model: string;
|
|
10
11
|
messages: ProviderTestMessage[];
|
|
11
12
|
max_tokens: number;
|
|
12
13
|
stream: boolean;
|
|
13
14
|
};
|
|
14
15
|
|
|
16
|
+
export type ProviderTestResponsesRequestBody = {
|
|
17
|
+
model: string;
|
|
18
|
+
input: string;
|
|
19
|
+
max_output_tokens: number;
|
|
20
|
+
stream: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ProviderTestRequestBody =
|
|
24
|
+
| ProviderTestChatRequestBody
|
|
25
|
+
| ProviderTestResponsesRequestBody;
|
|
26
|
+
|
|
15
27
|
const MEMORY_PROBE_FACTS = [
|
|
16
28
|
"Project fact: Agent Inspector captures LLM traffic and turns it into Raw Trace, Session Episode, and Memory Candidate layers.",
|
|
17
29
|
"Procedure: before release, run bun test, npm run typecheck, npm run lint, npm run build, and openspec validate --all --strict.",
|
|
@@ -63,7 +75,17 @@ export function buildProviderTestMessages(mode: ProviderTestMode): ProviderTestM
|
|
|
63
75
|
export function buildProviderTestRequestBody(
|
|
64
76
|
model: string,
|
|
65
77
|
mode: ProviderTestMode,
|
|
78
|
+
apiShape: ProviderTestApiShape = "chat",
|
|
66
79
|
): ProviderTestRequestBody {
|
|
80
|
+
if (apiShape === "responses") {
|
|
81
|
+
return {
|
|
82
|
+
model,
|
|
83
|
+
input: buildProviderTestUserPrompt(mode),
|
|
84
|
+
max_output_tokens: maxTokensForMode(mode),
|
|
85
|
+
stream: mode === "streaming",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
const base = {
|
|
68
90
|
model,
|
|
69
91
|
messages: buildProviderTestMessages(mode),
|