@tonyclaw/agent-inspector 2.1.13 → 2.1.15
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-BcEd6V-V.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +106 -0
- package/.output/public/assets/ReplayDialog-BNpC0548.js +1 -0
- package/.output/public/assets/{RequestAnatomy-BjlBT-Cy.js → RequestAnatomy-Ds1uRLVB.js} +1 -1
- package/.output/public/assets/ResponseView-7KPVqKl5.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-D2_SMhlE.js → StreamingChunkSequence-BHQT261s.js} +1 -1
- package/.output/public/assets/_sessionId-DWePGjnS.js +1 -0
- package/.output/public/assets/index-CI1-G8ua.js +1 -0
- package/.output/public/assets/index-DdhFqPsI.css +1 -0
- package/.output/public/assets/index-DjKt8XKe.js +14 -0
- package/.output/public/assets/json-viewer-CkCu-rka.js +1 -0
- package/.output/public/assets/{main-Dtspb4Ui.js → main-DpD1N0S8.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +194 -200
- package/.output/server/_libs/react-markdown.mjs +90 -2
- package/.output/server/{_sessionId-CXDcLuvi.mjs → _sessionId-DF9Sy8cP.mjs} +6 -57
- package/.output/server/_ssr/{CompareDrawer-CMoCAoeq.mjs → CompareDrawer-BoxztaO7.mjs} +50 -74
- package/.output/server/_ssr/{ProxyViewerContainer-BW2vVCBN.mjs → ProxyViewerContainer-CRBkqFlJ.mjs} +1221 -799
- package/.output/server/_ssr/{ReplayDialog-ChXL1t8H.mjs → ReplayDialog-Cc1dyDuK.mjs} +18 -62
- package/.output/server/_ssr/{RequestAnatomy-DtKzIlfU.mjs → RequestAnatomy-CMGSsz5Z.mjs} +6 -57
- package/.output/server/_ssr/{ResponseView-B5I8drzc.mjs → ResponseView-Cp10DM1D.mjs} +139 -61
- package/.output/server/_ssr/{StreamingChunkSequence-DvwjQNcO.mjs → StreamingChunkSequence-B1VGxy3A.mjs} +13 -58
- package/.output/server/_ssr/{index-D_ZHtRfl.mjs → index-47XVPghS.mjs} +6 -57
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-zDE2rrmJ.mjs +478 -0
- package/.output/server/_ssr/{router-DhL9Wp3N.mjs → router-DVeuZFqI.mjs} +680 -174
- package/.output/server/{_tanstack-start-manifest_v-BHgoAmxZ.mjs → _tanstack-start-manifest_v-Bp8JxtPW.mjs} +1 -1
- package/.output/server/index.mjs +72 -58
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +28 -2
- package/src/components/ProxyViewerContainer.tsx +25 -17
- package/src/components/clients/ClientLogo.tsx +132 -0
- package/src/components/groups/GroupsDialog.tsx +18 -13
- 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/AgentTraceSummary.tsx +56 -23
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
- package/src/components/proxy-viewer/CompareDrawer.tsx +58 -14
- package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
- package/src/components/proxy-viewer/ConversationHeader.tsx +33 -41
- package/src/components/proxy-viewer/LogEntry.tsx +41 -12
- package/src/components/proxy-viewer/LogEntryHeader.tsx +122 -51
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +60 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +8 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
- package/src/components/proxy-viewer/ThreadConnector.tsx +18 -7
- package/src/components/proxy-viewer/TurnGroup.tsx +67 -32
- 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 +77 -5
- 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/sessionInfoContract.ts +1 -0
- package/src/lib/stopReason.ts +59 -15
- 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/providerImporters.ts +45 -5
- package/src/proxy/providers.ts +31 -11
- package/src/proxy/schemas.ts +6 -0
- package/src/proxy/sessionInfo.ts +5 -1
- 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-Cz_1vIpR.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-7QSiluMf.js +0 -117
- package/.output/public/assets/ReplayDialog-sBA1KAYD.js +0 -1
- package/.output/public/assets/ResponseView-PtEKzml9.js +0 -1
- package/.output/public/assets/_sessionId-DZfB4ruK.js +0 -1
- package/.output/public/assets/index-B-QQLbpz.js +0 -1
- package/.output/public/assets/index-CmtfjQPv.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { JSX } from "react";
|
|
3
3
|
import {
|
|
4
4
|
Check,
|
|
@@ -23,15 +23,19 @@ import {
|
|
|
23
23
|
previewNode,
|
|
24
24
|
} from "./requestDiff";
|
|
25
25
|
import { getConversationId } from "./ConversationHeader";
|
|
26
|
-
import { JsonViewerFromString } from "../ui/json-viewer";
|
|
27
26
|
import { Badge } from "../ui/badge";
|
|
28
27
|
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
|
|
28
|
+
import { LazyJsonViewerFromString } from "./lazy";
|
|
29
29
|
|
|
30
30
|
export type CompareDrawerProps = {
|
|
31
31
|
/** Log selected first (shown on the left). */
|
|
32
32
|
left: CapturedLog;
|
|
33
33
|
/** Log selected second (shown on the right). */
|
|
34
34
|
right: CapturedLog;
|
|
35
|
+
/** Human-facing ordinal inside the left log's session. */
|
|
36
|
+
leftDisplayNumber: number;
|
|
37
|
+
/** Human-facing ordinal inside the right log's session. */
|
|
38
|
+
rightDisplayNumber: number;
|
|
35
39
|
onClose: () => void;
|
|
36
40
|
};
|
|
37
41
|
|
|
@@ -65,6 +69,20 @@ function nodeToJsonValue(node: JsonNode): unknown {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
function LazyJsonStringView({
|
|
73
|
+
text,
|
|
74
|
+
defaultExpandDepth,
|
|
75
|
+
}: {
|
|
76
|
+
text: string;
|
|
77
|
+
defaultExpandDepth: number;
|
|
78
|
+
}): JSX.Element {
|
|
79
|
+
return (
|
|
80
|
+
<Suspense fallback={<div className="text-xs text-muted-foreground">Loading JSON...</div>}>
|
|
81
|
+
<LazyJsonViewerFromString text={text} defaultExpandDepth={defaultExpandDepth} />
|
|
82
|
+
</Suspense>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
/** The parent path of a JSON path string. E.g. `messages[3].content` →
|
|
69
87
|
* `messages[3]`, `messages[3]` → `messages`, `messages` → `""`. */
|
|
70
88
|
function parentPath(path: string): string {
|
|
@@ -222,7 +240,7 @@ function EqualRunRow({
|
|
|
222
240
|
{ops.map((op) => (
|
|
223
241
|
<div key={op.path} className="border border-border/50 rounded p-2 bg-muted/20">
|
|
224
242
|
<div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
|
|
225
|
-
<
|
|
243
|
+
<LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
226
244
|
</div>
|
|
227
245
|
))}
|
|
228
246
|
</div>
|
|
@@ -393,7 +411,7 @@ function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.E
|
|
|
393
411
|
if (op.kind === "added" || op.kind === "removed") {
|
|
394
412
|
return (
|
|
395
413
|
<div className="pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20">
|
|
396
|
-
<
|
|
414
|
+
<LazyJsonStringView text={nodeToJsonString(op.value)} defaultExpandDepth={0} />
|
|
397
415
|
</div>
|
|
398
416
|
);
|
|
399
417
|
}
|
|
@@ -411,11 +429,11 @@ function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.E
|
|
|
411
429
|
<div className="pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
412
430
|
<div className="border border-rose-500/30 rounded p-2 bg-rose-500/5">
|
|
413
431
|
<div className="text-[10px] uppercase tracking-wider text-rose-500 mb-1">Old</div>
|
|
414
|
-
<
|
|
432
|
+
<LazyJsonStringView text={nodeToJsonString(op.left)} defaultExpandDepth={0} />
|
|
415
433
|
</div>
|
|
416
434
|
<div className="border border-emerald-500/30 rounded p-2 bg-emerald-500/5">
|
|
417
435
|
<div className="text-[10px] uppercase tracking-wider text-emerald-500 mb-1">New</div>
|
|
418
|
-
<
|
|
436
|
+
<LazyJsonStringView text={nodeToJsonString(op.right)} defaultExpandDepth={0} />
|
|
419
437
|
</div>
|
|
420
438
|
</div>
|
|
421
439
|
);
|
|
@@ -527,7 +545,15 @@ function ModeToggle({
|
|
|
527
545
|
);
|
|
528
546
|
}
|
|
529
547
|
|
|
530
|
-
function SideSummary({
|
|
548
|
+
function SideSummary({
|
|
549
|
+
log,
|
|
550
|
+
side,
|
|
551
|
+
displayNumber,
|
|
552
|
+
}: {
|
|
553
|
+
log: CapturedLog;
|
|
554
|
+
side: "left" | "right";
|
|
555
|
+
displayNumber: number;
|
|
556
|
+
}): JSX.Element {
|
|
531
557
|
const conversationId = getConversationId(log);
|
|
532
558
|
return (
|
|
533
559
|
<div className="flex-1 min-w-0 space-y-1 text-xs">
|
|
@@ -543,7 +569,9 @@ function SideSummary({ log, side }: { log: CapturedLog; side: "left" | "right" }
|
|
|
543
569
|
>
|
|
544
570
|
{side === "left" ? "← Left" : "Right →"}
|
|
545
571
|
</Badge>
|
|
546
|
-
<span className="font-mono text-blue-400/80"
|
|
572
|
+
<span className="font-mono text-blue-400/80" title={`Log ID ${String(log.id)}`}>
|
|
573
|
+
#{displayNumber}
|
|
574
|
+
</span>
|
|
547
575
|
{log.model !== null && (
|
|
548
576
|
<span className="font-mono text-muted-foreground truncate">{log.model}</span>
|
|
549
577
|
)}
|
|
@@ -570,7 +598,13 @@ function SideSummary({ log, side }: { log: CapturedLog; side: "left" | "right" }
|
|
|
570
598
|
);
|
|
571
599
|
}
|
|
572
600
|
|
|
573
|
-
export function CompareDrawer({
|
|
601
|
+
export function CompareDrawer({
|
|
602
|
+
left,
|
|
603
|
+
right,
|
|
604
|
+
leftDisplayNumber,
|
|
605
|
+
rightDisplayNumber,
|
|
606
|
+
onClose,
|
|
607
|
+
}: CompareDrawerProps): JSX.Element {
|
|
574
608
|
// Memoize the diff so re-renders (e.g. parent re-renders) don't recompute.
|
|
575
609
|
const ops = useMemo<DiffOp[]>(() => {
|
|
576
610
|
const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
|
|
@@ -727,8 +761,8 @@ export function CompareDrawer({ left, right, onClose }: CompareDrawerProps): JSX
|
|
|
727
761
|
{/* Header */}
|
|
728
762
|
<div className="flex items-start gap-4 px-4 py-3 border-b border-border">
|
|
729
763
|
<div className="flex-1 flex gap-4 min-w-0">
|
|
730
|
-
<SideSummary log={left} side="left" />
|
|
731
|
-
<SideSummary log={right} side="right" />
|
|
764
|
+
<SideSummary log={left} side="left" displayNumber={leftDisplayNumber} />
|
|
765
|
+
<SideSummary log={right} side="right" displayNumber={rightDisplayNumber} />
|
|
732
766
|
</div>
|
|
733
767
|
<div className="flex items-center gap-2 shrink-0">
|
|
734
768
|
<ModeToggle mode={mode} onChange={setMode} />
|
|
@@ -785,7 +819,13 @@ export function CompareDrawer({ left, right, onClose }: CompareDrawerProps): JSX
|
|
|
785
819
|
})}
|
|
786
820
|
</div>
|
|
787
821
|
) : (
|
|
788
|
-
<SplitBody
|
|
822
|
+
<SplitBody
|
|
823
|
+
grouped={grouped}
|
|
824
|
+
left={left}
|
|
825
|
+
right={right}
|
|
826
|
+
leftDisplayNumber={leftDisplayNumber}
|
|
827
|
+
rightDisplayNumber={rightDisplayNumber}
|
|
828
|
+
/>
|
|
789
829
|
)}
|
|
790
830
|
</div>
|
|
791
831
|
</>
|
|
@@ -800,18 +840,22 @@ function SplitBody({
|
|
|
800
840
|
grouped,
|
|
801
841
|
left,
|
|
802
842
|
right,
|
|
843
|
+
leftDisplayNumber,
|
|
844
|
+
rightDisplayNumber,
|
|
803
845
|
}: {
|
|
804
846
|
grouped: GroupedOp[];
|
|
805
847
|
left: CapturedLog;
|
|
806
848
|
right: CapturedLog;
|
|
849
|
+
leftDisplayNumber: number;
|
|
850
|
+
rightDisplayNumber: number;
|
|
807
851
|
}): JSX.Element {
|
|
808
852
|
return (
|
|
809
853
|
<div className="grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs">
|
|
810
854
|
{/* Column headers */}
|
|
811
855
|
<div className="grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground">
|
|
812
856
|
<span>Path</span>
|
|
813
|
-
<span>Left (Log #{
|
|
814
|
-
<span>Right (Log #{
|
|
857
|
+
<span title={`Log ID ${String(left.id)}`}>Left (Log #{leftDisplayNumber})</span>
|
|
858
|
+
<span title={`Log ID ${String(right.id)}`}>Right (Log #{rightDisplayNumber})</span>
|
|
815
859
|
</div>
|
|
816
860
|
|
|
817
861
|
{grouped.map((g, i) => {
|
|
@@ -4,7 +4,7 @@ import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
|
4
4
|
import type { CapturedLog } from "../../contracts";
|
|
5
5
|
import { ConversationHeader, type ConversationGroupData } from "./ConversationHeader";
|
|
6
6
|
import { detectProvider } from "../providers/ProviderLogo";
|
|
7
|
-
import {
|
|
7
|
+
import { TurnGroupList } from "./TurnGroupList";
|
|
8
8
|
import { AgentTraceSummary } from "./AgentTraceSummary";
|
|
9
9
|
import type { CacheTrendEntry } from "./cacheTrend";
|
|
10
10
|
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest } from "./logFocus";
|
|
@@ -180,20 +180,16 @@ export const ConversationGroup = memo(function ({
|
|
|
180
180
|
showRollupMetrics={showTraceRollupMetrics}
|
|
181
181
|
timeDisplayFormat={timeDisplayFormat}
|
|
182
182
|
/>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
comparisonPredecessors={comparisonPredecessors}
|
|
194
|
-
turnIndex={tg.turnIndex}
|
|
195
|
-
/>
|
|
196
|
-
))}
|
|
183
|
+
<TurnGroupList
|
|
184
|
+
turnGroups={turnGroups}
|
|
185
|
+
viewMode={viewMode}
|
|
186
|
+
strip={strip}
|
|
187
|
+
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
188
|
+
timeDisplayFormat={timeDisplayFormat}
|
|
189
|
+
cacheTrends={cacheTrends}
|
|
190
|
+
onCompareWithPrevious={onCompareWithPrevious}
|
|
191
|
+
comparisonPredecessors={comparisonPredecessors}
|
|
192
|
+
/>
|
|
197
193
|
</div>
|
|
198
194
|
)}
|
|
199
195
|
</div>
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
Loader2,
|
|
8
8
|
MessageSquare,
|
|
9
9
|
Trash2,
|
|
10
|
-
User,
|
|
11
10
|
Zap,
|
|
12
11
|
} from "lucide-react";
|
|
13
12
|
import type { JSX } from "react";
|
|
@@ -17,8 +16,9 @@ import { formatTimestampRange } from "../../lib/timeDisplay";
|
|
|
17
16
|
import { cn, formatTokens } from "../../lib/utils";
|
|
18
17
|
import type { CapturedLog } from "../../contracts";
|
|
19
18
|
import { ConfirmDialog } from "../ui/confirm-dialog";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
19
|
+
import type { Provider } from "../providers/ProviderLogo";
|
|
20
|
+
import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
|
|
21
|
+
import { ProviderLogoStack, ROW_CHEVRON_SLOT_CLASS } from "./ProviderLogoStack";
|
|
22
22
|
import { resolveLogFormat } from "./log-formats";
|
|
23
23
|
|
|
24
24
|
export type ConversationHeaderProps = {
|
|
@@ -89,6 +89,17 @@ export function ConversationHeader({
|
|
|
89
89
|
},
|
|
90
90
|
[conversationId],
|
|
91
91
|
);
|
|
92
|
+
const providerLogoItems =
|
|
93
|
+
providers?.map(({ provider, models }) => ({
|
|
94
|
+
provider,
|
|
95
|
+
tooltip: models.length === 1 ? (models[0] ?? provider) : models.join(", "),
|
|
96
|
+
})) ?? [];
|
|
97
|
+
const clientApp = detectClientApp({ userAgent, conversationId, clientProjectFolder });
|
|
98
|
+
const detectedClientLabel = clientAppLabel(clientApp);
|
|
99
|
+
const hasUserAgent = userAgent !== null && userAgent !== undefined && userAgent !== "";
|
|
100
|
+
const clientDisplayLabel = detectedClientLabel ?? userAgent ?? null;
|
|
101
|
+
const clientTooltip =
|
|
102
|
+
detectedClientLabel !== null && hasUserAgent ? `${detectedClientLabel} - ${userAgent}` : null;
|
|
92
103
|
|
|
93
104
|
return (
|
|
94
105
|
<div
|
|
@@ -112,14 +123,19 @@ export function ConversationHeader({
|
|
|
112
123
|
}
|
|
113
124
|
}}
|
|
114
125
|
>
|
|
126
|
+
{/* Provider logos: first visual element whenever model/provider logos are available. */}
|
|
127
|
+
<ProviderLogoStack items={providerLogoItems} ariaLabel="Models used in this session" />
|
|
128
|
+
|
|
115
129
|
{/* Expand chevron - shows spinner when collapsed and group has pending logs */}
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
<span className={ROW_CHEVRON_SLOT_CLASS}>
|
|
131
|
+
{expanded ? (
|
|
132
|
+
<ChevronDown className="size-4 shrink-0" />
|
|
133
|
+
) : isLoading ? (
|
|
134
|
+
<Loader2 className="size-4 shrink-0 animate-spin" />
|
|
135
|
+
) : (
|
|
136
|
+
<ChevronRight className="size-4 shrink-0" />
|
|
137
|
+
)}
|
|
138
|
+
</span>
|
|
123
139
|
|
|
124
140
|
{/* Session identifier.
|
|
125
141
|
- Real session id (e.g. `sess_abc123...`) → render the id with the
|
|
@@ -175,41 +191,17 @@ export function ConversationHeader({
|
|
|
175
191
|
</>
|
|
176
192
|
)}
|
|
177
193
|
|
|
178
|
-
{/* User-Agent */}
|
|
179
|
-
{
|
|
194
|
+
{/* Client app / User-Agent */}
|
|
195
|
+
{clientDisplayLabel !== null && clientDisplayLabel !== "" && (
|
|
180
196
|
<span
|
|
181
197
|
className="flex items-center gap-1 text-muted-foreground text-xs shrink-0"
|
|
182
|
-
title={
|
|
198
|
+
title={clientTooltip ?? clientDisplayLabel}
|
|
183
199
|
>
|
|
184
|
-
<
|
|
185
|
-
<span className="font-mono tabular-nums truncate max-w-[120px]">
|
|
186
|
-
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
{/* Provider logos — one per unique provider used in this session. When
|
|
190
|
-
a session switched models mid-stream (e.g. claude-3-5 → deepseek-v4)
|
|
191
|
-
multiple logos appear side-by-side; tooltip lists every distinct
|
|
192
|
-
model name. */}
|
|
193
|
-
{providers !== undefined && providers.length > 0 && (
|
|
194
|
-
<TooltipProvider delayDuration={150}>
|
|
195
|
-
<span
|
|
196
|
-
className="flex items-center gap-0.5 shrink-0"
|
|
197
|
-
aria-label="Models used in this session"
|
|
198
|
-
>
|
|
199
|
-
{providers.map(({ provider, models }) => (
|
|
200
|
-
<Tooltip key={provider}>
|
|
201
|
-
<TooltipTrigger asChild>
|
|
202
|
-
<span className="shrink-0">
|
|
203
|
-
<ProviderLogo provider={provider} className="size-4" />
|
|
204
|
-
</span>
|
|
205
|
-
</TooltipTrigger>
|
|
206
|
-
<TooltipContent>
|
|
207
|
-
{models.length === 1 ? models[0] : models.join(", ")}
|
|
208
|
-
</TooltipContent>
|
|
209
|
-
</Tooltip>
|
|
210
|
-
))}
|
|
200
|
+
<ClientLogo client={clientApp} className="size-5" />
|
|
201
|
+
<span className="font-mono tabular-nums truncate max-w-[120px]">
|
|
202
|
+
{clientDisplayLabel}
|
|
211
203
|
</span>
|
|
212
|
-
</
|
|
204
|
+
</span>
|
|
213
205
|
)}
|
|
214
206
|
|
|
215
207
|
{/* Time range */}
|
|
@@ -75,7 +75,7 @@ const BODY_CHUNK_BYTES = 256 * 1024;
|
|
|
75
75
|
const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
|
|
76
76
|
|
|
77
77
|
const TAB_TRIGGER_CLASS =
|
|
78
|
-
"h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none hover:bg-cyan-400/[0.
|
|
78
|
+
"h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:bg-cyan-400/[0.05] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-cyan-300/[0.04] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
|
|
79
79
|
|
|
80
80
|
function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
|
|
81
81
|
return {
|
|
@@ -140,7 +140,7 @@ function ExpandedPanel({
|
|
|
140
140
|
return (
|
|
141
141
|
<div
|
|
142
142
|
className={cn(
|
|
143
|
-
"inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-
|
|
143
|
+
"inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-cyan-300/10 bg-[#05070a] p-3 shadow-[0_16px_45px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.05)]",
|
|
144
144
|
className,
|
|
145
145
|
)}
|
|
146
146
|
>
|
|
@@ -277,6 +277,8 @@ function HeaderRows({
|
|
|
277
277
|
|
|
278
278
|
export type LogEntryProps = {
|
|
279
279
|
log: CapturedLog;
|
|
280
|
+
/** Human-facing ordinal inside the current session. Internal lookups still use `log.id`. */
|
|
281
|
+
displayNumber: number;
|
|
280
282
|
viewMode?: "simple" | "full";
|
|
281
283
|
/** Live "strip Claude Code billing header" flag, sourced once at the viewer
|
|
282
284
|
* container. Hoisted out of `LogEntry` so a single SWR subscription serves
|
|
@@ -361,6 +363,7 @@ const HeadersDiffContent = memo(function ({
|
|
|
361
363
|
|
|
362
364
|
export const LogEntry = memo(function ({
|
|
363
365
|
log,
|
|
366
|
+
displayNumber,
|
|
364
367
|
viewMode = "simple",
|
|
365
368
|
strip,
|
|
366
369
|
slowResponseThresholdSeconds,
|
|
@@ -384,6 +387,7 @@ export const LogEntry = memo(function ({
|
|
|
384
387
|
);
|
|
385
388
|
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
386
389
|
const logIdRef = useRef(log.id);
|
|
390
|
+
const bodyHydrationInFlightRef = useRef(false);
|
|
387
391
|
const displayLog = hydratedLog ?? log;
|
|
388
392
|
const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
|
|
389
393
|
const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
|
|
@@ -421,10 +425,10 @@ export const LogEntry = memo(function ({
|
|
|
421
425
|
requestPreviewShouldParse ? displayedRequestBody : null,
|
|
422
426
|
);
|
|
423
427
|
const rawRequestExpansion = useJsonBulkExpansion(
|
|
424
|
-
requestPreviewShouldParse ? rawRequestBodyForView : null,
|
|
428
|
+
activeTab === "raw-request" && requestPreviewShouldParse ? rawRequestBodyForView : null,
|
|
425
429
|
);
|
|
426
430
|
const responseExpansion = useJsonBulkExpansion(
|
|
427
|
-
responsePreviewShouldParse ? responseTextForView : null,
|
|
431
|
+
activeTab === "raw" && responsePreviewShouldParse ? responseTextForView : null,
|
|
428
432
|
);
|
|
429
433
|
|
|
430
434
|
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
@@ -634,6 +638,7 @@ export const LogEntry = memo(function ({
|
|
|
634
638
|
|
|
635
639
|
useEffect(() => {
|
|
636
640
|
logIdRef.current = log.id;
|
|
641
|
+
bodyHydrationInFlightRef.current = false;
|
|
637
642
|
setHydratedLog(null);
|
|
638
643
|
setBodyHydrationState("idle");
|
|
639
644
|
setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
|
|
@@ -643,9 +648,10 @@ export const LogEntry = memo(function ({
|
|
|
643
648
|
useEffect(() => {
|
|
644
649
|
if (!expanded) return;
|
|
645
650
|
if (!shouldHydrateLogBody(log)) return;
|
|
646
|
-
if (hydratedLog !== null ||
|
|
651
|
+
if (hydratedLog !== null || bodyHydrationInFlightRef.current) return;
|
|
647
652
|
|
|
648
653
|
let cancelled = false;
|
|
654
|
+
bodyHydrationInFlightRef.current = true;
|
|
649
655
|
setBodyHydrationState("loading");
|
|
650
656
|
void fetchJsonWithTimeout(
|
|
651
657
|
`/api/logs/${String(log.id)}`,
|
|
@@ -654,19 +660,21 @@ export const LogEntry = memo(function ({
|
|
|
654
660
|
)
|
|
655
661
|
.then((fullLog) => {
|
|
656
662
|
if (cancelled) return;
|
|
663
|
+
bodyHydrationInFlightRef.current = false;
|
|
657
664
|
setHydratedLog(fullLog);
|
|
658
665
|
setBodyHydrationState("idle");
|
|
659
666
|
})
|
|
660
667
|
.catch(() => {
|
|
661
668
|
if (cancelled) return;
|
|
669
|
+
bodyHydrationInFlightRef.current = false;
|
|
662
670
|
setBodyHydrationState("failed");
|
|
663
671
|
});
|
|
664
672
|
|
|
665
673
|
return () => {
|
|
666
674
|
cancelled = true;
|
|
675
|
+
bodyHydrationInFlightRef.current = false;
|
|
667
676
|
};
|
|
668
677
|
}, [
|
|
669
|
-
bodyHydrationState,
|
|
670
678
|
expanded,
|
|
671
679
|
hydratedLog,
|
|
672
680
|
log.bodyContentMode,
|
|
@@ -710,14 +718,26 @@ export const LogEntry = memo(function ({
|
|
|
710
718
|
<TooltipProvider>
|
|
711
719
|
<div
|
|
712
720
|
className={cn(
|
|
713
|
-
"mb-2 overflow-hidden rounded-
|
|
721
|
+
"relative mb-2 overflow-hidden rounded-lg border bg-[#07090d] transition-[background-color,border-color,box-shadow]",
|
|
714
722
|
expanded
|
|
715
|
-
? "border-cyan-300/
|
|
716
|
-
: "border-
|
|
723
|
+
? "border-cyan-300/25 shadow-[0_24px_80px_rgba(0,0,0,0.34),0_0_0_1px_rgba(103,232,249,0.04),inset_0_1px_0_rgba(255,255,255,0.06)]"
|
|
724
|
+
: "border-white/10 shadow-[0_6px_24px_rgba(0,0,0,0.16)] hover:border-cyan-300/15",
|
|
717
725
|
)}
|
|
718
726
|
>
|
|
727
|
+
<div
|
|
728
|
+
className={cn(
|
|
729
|
+
"pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
|
|
730
|
+
expanded ? "bg-cyan-300/45 shadow-[0_0_20px_rgba(103,232,249,0.28)]" : "bg-white/10",
|
|
731
|
+
)}
|
|
732
|
+
aria-hidden="true"
|
|
733
|
+
/>
|
|
734
|
+
<div
|
|
735
|
+
className="pointer-events-none absolute inset-x-3 top-0 h-px bg-gradient-to-r from-transparent via-white/15 to-transparent"
|
|
736
|
+
aria-hidden="true"
|
|
737
|
+
/>
|
|
719
738
|
<LogEntryHeader
|
|
720
739
|
log={displayLog}
|
|
740
|
+
displayNumber={displayNumber}
|
|
721
741
|
messageCount={requestAnalysis.messageCount}
|
|
722
742
|
toolCount={requestAnalysis.toolCount}
|
|
723
743
|
expanded={expanded}
|
|
@@ -738,14 +758,18 @@ export const LogEntry = memo(function ({
|
|
|
738
758
|
|
|
739
759
|
{expanded && (
|
|
740
760
|
<div
|
|
741
|
-
className="border-t border-
|
|
761
|
+
className="relative border-t border-cyan-300/10 bg-[linear-gradient(180deg,#06090d_0%,#040506_100%)] pt-2"
|
|
742
762
|
onClick={(e) => e.stopPropagation()}
|
|
743
763
|
onKeyDown={(e) => e.stopPropagation()}
|
|
744
764
|
>
|
|
765
|
+
<div
|
|
766
|
+
className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-cyan-300/[0.035] to-transparent"
|
|
767
|
+
aria-hidden="true"
|
|
768
|
+
/>
|
|
745
769
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
|
|
746
770
|
<TabsList
|
|
747
771
|
variant="line"
|
|
748
|
-
className="inspector-scrollbar mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-
|
|
772
|
+
className="inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-cyan-300/10 bg-black/10 p-0"
|
|
749
773
|
>
|
|
750
774
|
{viewMode === "full" && (
|
|
751
775
|
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
|
|
@@ -1031,7 +1055,12 @@ export const LogEntry = memo(function ({
|
|
|
1031
1055
|
)}
|
|
1032
1056
|
</div>
|
|
1033
1057
|
<Suspense fallback={null}>
|
|
1034
|
-
<LazyReplayDialog
|
|
1058
|
+
<LazyReplayDialog
|
|
1059
|
+
log={displayLog}
|
|
1060
|
+
displayNumber={displayNumber}
|
|
1061
|
+
open={replayOpen}
|
|
1062
|
+
onOpenChange={setReplayOpen}
|
|
1063
|
+
/>
|
|
1035
1064
|
</Suspense>
|
|
1036
1065
|
</TooltipProvider>
|
|
1037
1066
|
);
|