@tonyclaw/agent-inspector 3.0.14 → 3.0.16
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-DDfwHQUi.js → CompareDrawer-BJmxAiM0.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-C40Nig73.js +106 -0
- package/.output/public/assets/{ReplayDialog-D-26DMBt.js → ReplayDialog-CkLdOMJr.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-B6zNFqNX.js → RequestAnatomy-DemOFfSc.js} +1 -1
- package/.output/public/assets/{ResponseView-Ccy95rWI.js → ResponseView-9LhpuDo7.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-yIiscGuE.js → StreamingChunkSequence-RRWYBOdz.js} +1 -1
- package/.output/public/assets/{_sessionId-BnMB0zab.js → _sessionId-CWEh2Jsn.js} +1 -1
- package/.output/public/assets/index-HRfrFVYw.css +1 -0
- package/.output/public/assets/{index-BbtAIxbg.js → index-Lwir70Rs.js} +1 -1
- package/.output/public/assets/index-hjNWJZGE.js +1 -0
- package/.output/public/assets/{json-viewer-C838cVmD.js → json-viewer-D8gvbDlB.js} +1 -1
- package/.output/public/assets/{main-BfN4culo.js → main-ovrz0tyD.js} +7 -7
- package/.output/server/_libs/lucide-react.mjs +215 -201
- package/.output/server/{_sessionId-DoprmwZV.mjs → _sessionId-zI_GFX_7.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-YMLJNxXR.mjs → CompareDrawer-Ckcx1RVu.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-C62eZnva.mjs → ProxyViewerContainer-BgXewlIr.mjs} +729 -378
- package/.output/server/_ssr/{ReplayDialog-NmN7Ukjo.mjs → ReplayDialog-wx3nDtZn.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-Dawm2qxe.mjs → RequestAnatomy-DN80zt0G.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-BHyAY83t.mjs → ResponseView-DD7rTu_l.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-C4qgcJbg.mjs → StreamingChunkSequence-DAgz6Vvw.mjs} +3 -3
- package/.output/server/_ssr/{index-B2Pf6W_T.mjs → index-dcTjp8du.mjs} +14 -5
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-u67BASf8.mjs → json-viewer-N2cuzGWR.mjs} +3 -3
- package/.output/server/_ssr/{router-CZhFVMGN.mjs → router-BfQ0QsD5.mjs} +132 -17
- package/.output/server/{_tanstack-start-manifest_v-Dk2HfOoe.mjs → _tanstack-start-manifest_v-BXnYrbcJ.mjs} +1 -1
- package/.output/server/index.mjs +75 -75
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +475 -197
- package/src/components/ProxyViewerContainer.tsx +145 -17
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +36 -10
- package/src/components/proxy-viewer/TurnGroup.tsx +4 -1
- package/src/lib/chunkLoadRecovery.ts +83 -0
- package/src/proxy/formats/openai/codexMetadata.ts +61 -0
- package/src/proxy/formats/openai/handler.ts +8 -2
- package/src/routes/__root.tsx +6 -3
- package/src/routes/index.tsx +25 -1
- package/src/routes/session/$sessionId.tsx +1 -0
- package/styles/globals.css +54 -0
- package/.output/public/assets/ProxyViewerContainer-CbNsaoQw.js +0 -106
- package/.output/public/assets/index-49RC4dlD.js +0 -1
- package/.output/public/assets/index-DXKupW0r.css +0 -1
|
@@ -2,7 +2,11 @@ import { useState, useEffect, useCallback, useRef, useMemo, useTransition, type
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CapturedLogSchema, type CapturedLog } from "../contracts";
|
|
4
4
|
import { fetchJson, fetchJsonWithTimeout } from "../lib/apiClient";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DeleteInspectorGroupsResponseSchema,
|
|
7
|
+
InspectorGroupsListResponseSchema,
|
|
8
|
+
type InspectorGroup,
|
|
9
|
+
} from "../lib/groupContract";
|
|
6
10
|
import { ImportLogsResponseSchema, type ImportLogsResponse } from "../lib/logImportContract";
|
|
7
11
|
import { useStripConfig } from "../lib/useStripConfig";
|
|
8
12
|
import { OnboardingBanner } from "./OnboardingBanner";
|
|
@@ -58,6 +62,11 @@ const DeleteLogsResponseSchema = z.object({
|
|
|
58
62
|
sessionArchiveLogsDeleted: z.number().int().nonnegative().optional(),
|
|
59
63
|
});
|
|
60
64
|
|
|
65
|
+
type GroupCleanupResult = {
|
|
66
|
+
deleted: number;
|
|
67
|
+
ok: boolean;
|
|
68
|
+
};
|
|
69
|
+
|
|
61
70
|
type SessionPageRequest =
|
|
62
71
|
| { kind: "newest" }
|
|
63
72
|
| { kind: "oldest" }
|
|
@@ -190,6 +199,50 @@ function fetchSessionLogsPage(
|
|
|
190
199
|
);
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
type SessionLogIdCollection = {
|
|
203
|
+
ids: number[];
|
|
204
|
+
truncated: boolean;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
async function collectSessionLogIds(
|
|
208
|
+
sessionId: string,
|
|
209
|
+
onProgress: (logCount: number, pageCount: number) => void,
|
|
210
|
+
): Promise<SessionLogIdCollection> {
|
|
211
|
+
const ids = new Set<number>();
|
|
212
|
+
let page = await fetchSessionLogsPage(sessionId, { kind: "oldest" }, SESSION_RELOAD_PAGE_LIMIT);
|
|
213
|
+
for (const log of page.logs) {
|
|
214
|
+
ids.add(log.id);
|
|
215
|
+
}
|
|
216
|
+
onProgress(ids.size, 1);
|
|
217
|
+
|
|
218
|
+
let loadedPages = 1;
|
|
219
|
+
while (page.hasNewer && page.newestLogId !== null && loadedPages < MAX_SESSION_RELOAD_PAGES) {
|
|
220
|
+
page = await fetchSessionLogsPage(
|
|
221
|
+
sessionId,
|
|
222
|
+
{ kind: "newer", afterLogId: page.newestLogId },
|
|
223
|
+
SESSION_RELOAD_PAGE_LIMIT,
|
|
224
|
+
);
|
|
225
|
+
for (const log of page.logs) {
|
|
226
|
+
ids.add(log.id);
|
|
227
|
+
}
|
|
228
|
+
loadedPages += 1;
|
|
229
|
+
onProgress(ids.size, loadedPages);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { ids: [...ids], truncated: page.hasNewer };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function deleteAllGroupsForFullHistoryDelete(): Promise<GroupCleanupResult> {
|
|
236
|
+
try {
|
|
237
|
+
const result = await fetchJson("/api/groups", DeleteInspectorGroupsResponseSchema, {
|
|
238
|
+
method: "DELETE",
|
|
239
|
+
});
|
|
240
|
+
return { deleted: result.deleted, ok: true };
|
|
241
|
+
} catch {
|
|
242
|
+
return { deleted: 0, ok: false };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
193
246
|
export function mergeLogsById(
|
|
194
247
|
previousLogs: readonly CapturedLog[],
|
|
195
248
|
incomingLogs: readonly CapturedLog[],
|
|
@@ -685,27 +738,88 @@ export function ProxyViewerContainer({
|
|
|
685
738
|
[showToast],
|
|
686
739
|
);
|
|
687
740
|
|
|
688
|
-
const handleDeleteCurrentView = useCallback(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
741
|
+
const handleDeleteCurrentView = useCallback(async (): Promise<void> => {
|
|
742
|
+
const deleteRequestId = sessionPageRequestIdRef.current + 1;
|
|
743
|
+
sessionPageRequestIdRef.current = deleteRequestId;
|
|
744
|
+
setSessionPageLoading(true);
|
|
745
|
+
stopSessionLoadProgress();
|
|
746
|
+
dismissToast();
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
if (selectedSession === "__all__") {
|
|
750
|
+
startSessionLoadProgress("Deleting stored history", deleteRequestId);
|
|
751
|
+
const result = await fetchJson("/api/logs", DeleteLogsResponseSchema, {
|
|
752
|
+
method: "DELETE",
|
|
753
|
+
});
|
|
754
|
+
const groupCleanup = await deleteAllGroupsForFullHistoryDelete();
|
|
755
|
+
if (sessionPageRequestIdRef.current !== deleteRequestId) return;
|
|
756
|
+
setAllLogs([]);
|
|
757
|
+
logIndexRef.current.clear();
|
|
758
|
+
setLogPage(null);
|
|
759
|
+
setKnownSessions([]);
|
|
760
|
+
setSessionMemberships([]);
|
|
761
|
+
setSessionPageLoading(false);
|
|
762
|
+
stopSessionLoadProgress();
|
|
763
|
+
const groupMessage = groupCleanup.ok
|
|
764
|
+
? ` Cleared ${groupCleanup.deleted.toString()} group${
|
|
765
|
+
groupCleanup.deleted === 1 ? "" : "s"
|
|
766
|
+
}.`
|
|
767
|
+
: " Group cleanup failed; open Groups to retry Clear.";
|
|
768
|
+
showToast({
|
|
769
|
+
message: `Deleted ${result.cleared.toString()} stored log${
|
|
770
|
+
result.cleared === 1 ? "" : "s"
|
|
771
|
+
} across all sessions.${groupMessage}`,
|
|
772
|
+
tone: groupCleanup.ok ? "success" : "error",
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const sessionId = selectedSession;
|
|
778
|
+
startSessionLoadProgress("Deleting session history", deleteRequestId);
|
|
779
|
+
const collected = await collectSessionLogIds(sessionId, (logCount, pageCount) => {
|
|
780
|
+
updateSessionLoadProgress("Deleting session history", deleteRequestId, logCount, pageCount);
|
|
781
|
+
});
|
|
782
|
+
const idSet = new Set(collected.ids);
|
|
783
|
+
for (const log of allLogs) {
|
|
784
|
+
if (log.sessionId === sessionId) {
|
|
785
|
+
idSet.add(log.id);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const persistedIds = [...idSet].filter((id) => Number.isInteger(id) && id >= 0);
|
|
692
789
|
if (persistedIds.length === 0) {
|
|
790
|
+
if (sessionPageRequestIdRef.current !== deleteRequestId) return;
|
|
791
|
+
setAllLogs((prev) => {
|
|
792
|
+
const remaining = prev.filter((log) => log.sessionId !== sessionId);
|
|
793
|
+
logIndexRef.current = buildLogIndex(remaining);
|
|
794
|
+
return remaining;
|
|
795
|
+
});
|
|
796
|
+
setLogPage((prev) =>
|
|
797
|
+
prev === null
|
|
798
|
+
? null
|
|
799
|
+
: {
|
|
800
|
+
...prev,
|
|
801
|
+
logs: prev.logs.filter((log) => log.sessionId !== sessionId),
|
|
802
|
+
},
|
|
803
|
+
);
|
|
804
|
+
setKnownSessions((prev) => prev.filter((session) => session !== sessionId));
|
|
805
|
+
setSessionPageLoading(false);
|
|
806
|
+
stopSessionLoadProgress();
|
|
693
807
|
showToast({
|
|
694
|
-
message: "No
|
|
695
|
-
tone: "
|
|
808
|
+
message: "No stored log records were found for this session.",
|
|
809
|
+
tone: "success",
|
|
696
810
|
});
|
|
697
811
|
return;
|
|
698
812
|
}
|
|
699
|
-
|
|
700
|
-
const removed = allLogs.filter((log) => idSet.has(log.id));
|
|
813
|
+
|
|
701
814
|
const result = await fetchJson("/api/logs", DeleteLogsResponseSchema, {
|
|
702
815
|
method: "DELETE",
|
|
703
816
|
headers: { "content-type": "application/json" },
|
|
704
817
|
body: JSON.stringify({ ids: persistedIds }),
|
|
705
818
|
});
|
|
706
819
|
|
|
820
|
+
if (sessionPageRequestIdRef.current !== deleteRequestId) return;
|
|
707
821
|
setAllLogs((prev) => {
|
|
708
|
-
const remaining = prev.filter((log) =>
|
|
822
|
+
const remaining = prev.filter((log) => log.sessionId !== sessionId);
|
|
709
823
|
logIndexRef.current = buildLogIndex(remaining);
|
|
710
824
|
return remaining;
|
|
711
825
|
});
|
|
@@ -714,19 +828,33 @@ export function ProxyViewerContainer({
|
|
|
714
828
|
? null
|
|
715
829
|
: {
|
|
716
830
|
...prev,
|
|
717
|
-
logs: prev.logs.filter((log) =>
|
|
831
|
+
logs: prev.logs.filter((log) => log.sessionId !== sessionId),
|
|
718
832
|
},
|
|
719
833
|
);
|
|
720
|
-
setKnownSessions((prev) =>
|
|
834
|
+
setKnownSessions((prev) => prev.filter((session) => session !== sessionId));
|
|
835
|
+
setSessionPageLoading(false);
|
|
836
|
+
stopSessionLoadProgress();
|
|
721
837
|
showToast({
|
|
722
838
|
message: `Deleted ${result.cleared.toString()} stored log${
|
|
723
839
|
result.cleared === 1 ? "" : "s"
|
|
724
|
-
}
|
|
725
|
-
tone: "success",
|
|
840
|
+
} for this session${collected.truncated ? ". More history may remain." : "."}`,
|
|
841
|
+
tone: collected.truncated ? "error" : "success",
|
|
726
842
|
});
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
843
|
+
} finally {
|
|
844
|
+
if (sessionPageRequestIdRef.current === deleteRequestId) {
|
|
845
|
+
setSessionPageLoading(false);
|
|
846
|
+
stopSessionLoadProgress();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}, [
|
|
850
|
+
allLogs,
|
|
851
|
+
dismissToast,
|
|
852
|
+
selectedSession,
|
|
853
|
+
showToast,
|
|
854
|
+
startSessionLoadProgress,
|
|
855
|
+
stopSessionLoadProgress,
|
|
856
|
+
updateSessionLoadProgress,
|
|
857
|
+
]);
|
|
730
858
|
|
|
731
859
|
const handleReloadSession = useCallback(() => {
|
|
732
860
|
if (initialSessionId !== undefined) {
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { type JSX } from "react";
|
|
2
|
-
import { Wrench } from "lucide-react";
|
|
2
|
+
import { AlertTriangle, Wrench } from "lucide-react";
|
|
3
3
|
import type { ToolTraceEvent } from "./viewerState";
|
|
4
4
|
import { dispatchLogFocusRequest } from "./logFocus";
|
|
5
5
|
|
|
6
6
|
type ToolTraceEventsProps = {
|
|
7
7
|
events: ToolTraceEvent[];
|
|
8
|
+
warnings?: readonly string[];
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
const VISIBLE_TOOL_LIMIT = 4;
|
|
12
|
+
|
|
10
13
|
function ToolTraceEventChip({ event }: { event: ToolTraceEvent }): JSX.Element {
|
|
11
14
|
return (
|
|
12
15
|
<button
|
|
13
16
|
type="button"
|
|
14
17
|
key={event.id}
|
|
15
|
-
className="inline-flex min-w-0 items-center gap-1
|
|
18
|
+
className="inline-flex h-6 max-w-[11rem] min-w-0 items-center gap-1 rounded-md bg-white/[0.028] px-1.5 text-[11px] text-muted-foreground transition-colors hover:bg-sky-400/[0.08] hover:text-sky-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
16
19
|
onClick={() =>
|
|
17
20
|
dispatchLogFocusRequest({
|
|
18
21
|
logId: event.logId,
|
|
@@ -21,21 +24,44 @@ function ToolTraceEventChip({ event }: { event: ToolTraceEvent }): JSX.Element {
|
|
|
21
24
|
})
|
|
22
25
|
}
|
|
23
26
|
aria-label={`Open response tool call ${event.name}`}
|
|
24
|
-
title={`Open ${event.name} in Response`}
|
|
25
27
|
>
|
|
26
|
-
<Wrench className="size-3
|
|
28
|
+
<Wrench className="size-3 shrink-0 text-sky-400/65" />
|
|
27
29
|
<span className="min-w-0 truncate font-mono font-semibold">{event.name}</span>
|
|
28
30
|
</button>
|
|
29
31
|
);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
export function ToolTraceEvents({
|
|
33
|
-
|
|
34
|
+
export function ToolTraceEvents({
|
|
35
|
+
events,
|
|
36
|
+
warnings = [],
|
|
37
|
+
}: ToolTraceEventsProps): JSX.Element | null {
|
|
38
|
+
if (events.length === 0 && warnings.length === 0) return null;
|
|
39
|
+
const visibleEvents = events.slice(0, VISIBLE_TOOL_LIMIT);
|
|
40
|
+
const hiddenCount = Math.max(0, events.length - visibleEvents.length);
|
|
34
41
|
return (
|
|
35
|
-
<div className="mx-3 mb-2 flex
|
|
36
|
-
{events.
|
|
37
|
-
<
|
|
38
|
-
|
|
42
|
+
<div className="mx-3 mb-2 flex min-w-0 items-center gap-1.5 overflow-hidden rounded-md bg-black/[0.10] px-2 py-1 shadow-[inset_0_1px_0_rgba(255,255,255,0.025)]">
|
|
43
|
+
{events.length > 0 && (
|
|
44
|
+
<span className="inline-flex h-6 shrink-0 items-center gap-1 rounded-md px-1.5 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] text-sky-300/70">
|
|
45
|
+
<Wrench className="size-3" />
|
|
46
|
+
{events.length}
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
{warnings.length > 0 && (
|
|
50
|
+
<span className="inline-flex h-6 shrink-0 items-center gap-1 rounded-md border border-amber-300/20 bg-amber-300/[0.08] px-1.5 font-mono text-[10px] font-semibold text-amber-100">
|
|
51
|
+
<AlertTriangle className="size-3" />
|
|
52
|
+
{warnings.length}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
<div className="flex min-w-0 flex-1 items-center gap-1 overflow-hidden">
|
|
56
|
+
{visibleEvents.map((event) => (
|
|
57
|
+
<ToolTraceEventChip key={event.id} event={event} />
|
|
58
|
+
))}
|
|
59
|
+
{hiddenCount > 0 && (
|
|
60
|
+
<span className="inline-flex h-6 shrink-0 items-center rounded-md bg-white/[0.025] px-1.5 font-mono text-[11px] font-semibold text-muted-foreground">
|
|
61
|
+
+{hiddenCount}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
39
65
|
</div>
|
|
40
66
|
);
|
|
41
67
|
}
|
|
@@ -361,7 +361,10 @@ export const TurnGroup = memo(function TurnGroup({
|
|
|
361
361
|
comparisonPredecessors.has(log.id) ? onCompareWithPrevious : undefined
|
|
362
362
|
}
|
|
363
363
|
/>
|
|
364
|
-
<ToolTraceEvents
|
|
364
|
+
<ToolTraceEvents
|
|
365
|
+
events={toolEventsByLogId.get(log.id) ?? []}
|
|
366
|
+
warnings={log.warnings ?? []}
|
|
367
|
+
/>
|
|
365
368
|
</div>
|
|
366
369
|
</div>
|
|
367
370
|
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const CHUNK_LOAD_RELOAD_AT_KEY = "agent-inspector.chunkLoadReloadAt";
|
|
2
|
+
const CHUNK_LOAD_RELOAD_WINDOW_MS = 15_000;
|
|
3
|
+
|
|
4
|
+
const CHUNK_LOAD_FAILURE_PATTERNS = [
|
|
5
|
+
"failed to fetch dynamically imported module",
|
|
6
|
+
"failed to load module script",
|
|
7
|
+
"importing a module script failed",
|
|
8
|
+
"error loading dynamically imported module",
|
|
9
|
+
"loading chunk",
|
|
10
|
+
"unable to preload css",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function messageFromReason(reason: unknown): string {
|
|
14
|
+
if (reason instanceof Error) {
|
|
15
|
+
return reason.message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof reason === "string") {
|
|
19
|
+
return reason;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isChunkLoadFailureMessage(message: string): boolean {
|
|
26
|
+
const normalizedMessage = message.toLowerCase();
|
|
27
|
+
return CHUNK_LOAD_FAILURE_PATTERNS.some((pattern) => normalizedMessage.includes(pattern));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readLastReloadAt(): number {
|
|
31
|
+
const storedValue = window.sessionStorage.getItem(CHUNK_LOAD_RELOAD_AT_KEY);
|
|
32
|
+
if (storedValue === null) {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parsedValue = Number(storedValue);
|
|
37
|
+
return Number.isFinite(parsedValue) ? parsedValue : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function reloadOnceForChunkFailure(): void {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
const lastReloadAt = readLastReloadAt();
|
|
43
|
+
if (now - lastReloadAt < CHUNK_LOAD_RELOAD_WINDOW_MS) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
window.sessionStorage.setItem(CHUNK_LOAD_RELOAD_AT_KEY, String(now));
|
|
48
|
+
window.location.reload();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function installChunkLoadRecovery(): () => void {
|
|
52
|
+
if (typeof window === "undefined") {
|
|
53
|
+
return () => {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
|
57
|
+
const message = messageFromReason(event.reason);
|
|
58
|
+
if (!isChunkLoadFailureMessage(message)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
reloadOnceForChunkFailure();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleError = (event: ErrorEvent): void => {
|
|
67
|
+
const message = event.message !== "" ? event.message : messageFromReason(event.error);
|
|
68
|
+
if (!isChunkLoadFailureMessage(message)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
reloadOnceForChunkFailure();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
|
77
|
+
window.addEventListener("error", handleError);
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener("unhandledrejection", handleUnhandledRejection);
|
|
81
|
+
window.removeEventListener("error", handleError);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type CodexClientMetadata = {
|
|
2
|
+
sessionId: string | null;
|
|
3
|
+
threadId: string | null;
|
|
4
|
+
turnId: string | null;
|
|
5
|
+
installationId: string | null;
|
|
6
|
+
windowId: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const EMPTY_CODEX_CLIENT_METADATA: CodexClientMetadata = {
|
|
10
|
+
sessionId: null,
|
|
11
|
+
threadId: null,
|
|
12
|
+
turnId: null,
|
|
13
|
+
installationId: null,
|
|
14
|
+
windowId: null,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function readObjectField(value: unknown, field: string): unknown {
|
|
18
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
19
|
+
return Object.getOwnPropertyDescriptor(value, field)?.value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readStringField(value: unknown, field: string): string | null {
|
|
23
|
+
const fieldValue = readObjectField(value, field);
|
|
24
|
+
return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseNestedTurnMetadata(value: unknown): unknown {
|
|
28
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(value);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractCodexClientMetadata(rawRequest: unknown): CodexClientMetadata {
|
|
37
|
+
const clientMetadata = readObjectField(rawRequest, "client_metadata");
|
|
38
|
+
if (clientMetadata === undefined) return EMPTY_CODEX_CLIENT_METADATA;
|
|
39
|
+
|
|
40
|
+
const turnMetadata = parseNestedTurnMetadata(
|
|
41
|
+
readObjectField(clientMetadata, "x-codex-turn-metadata"),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
sessionId:
|
|
46
|
+
readStringField(clientMetadata, "session_id") ?? readStringField(turnMetadata, "session_id"),
|
|
47
|
+
threadId:
|
|
48
|
+
readStringField(clientMetadata, "thread_id") ?? readStringField(turnMetadata, "thread_id"),
|
|
49
|
+
turnId: readStringField(clientMetadata, "turn_id") ?? readStringField(turnMetadata, "turn_id"),
|
|
50
|
+
installationId:
|
|
51
|
+
readStringField(clientMetadata, "x-codex-installation-id") ??
|
|
52
|
+
readStringField(turnMetadata, "installation_id"),
|
|
53
|
+
windowId:
|
|
54
|
+
readStringField(clientMetadata, "x-codex-window-id") ??
|
|
55
|
+
readStringField(turnMetadata, "window_id"),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extractCodexSessionId(rawRequest: unknown): string | null {
|
|
60
|
+
return extractCodexClientMetadata(rawRequest).sessionId;
|
|
61
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
parseOpenAIResponse,
|
|
8
8
|
} from "./schemas";
|
|
9
9
|
import { extractOpenAIStream } from "./stream";
|
|
10
|
+
import { extractCodexSessionId } from "./codexMetadata";
|
|
10
11
|
|
|
11
12
|
function emptyTokens(): TokenUsage {
|
|
12
13
|
return {
|
|
@@ -17,6 +18,11 @@ function emptyTokens(): TokenUsage {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function headerSessionId(headers: Headers | undefined): string | null {
|
|
22
|
+
if (headers === undefined) return null;
|
|
23
|
+
return headers.get("x-agent-inspector-session-id") ?? headers.get("x-session-affinity");
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
export const OpenAIFormatHandler: FormatHandler = {
|
|
21
27
|
format: "openai",
|
|
22
28
|
|
|
@@ -27,14 +33,14 @@ export const OpenAIFormatHandler: FormatHandler = {
|
|
|
27
33
|
if (result.success) {
|
|
28
34
|
return {
|
|
29
35
|
model: result.data.model,
|
|
30
|
-
sessionId:
|
|
36
|
+
sessionId: extractCodexSessionId(json) ?? headerSessionId(headers),
|
|
31
37
|
};
|
|
32
38
|
}
|
|
33
39
|
const responsesResult = OpenAIResponsesRequestSchema.safeParse(json);
|
|
34
40
|
if (responsesResult.success) {
|
|
35
41
|
return {
|
|
36
42
|
model: responsesResult.data.model,
|
|
37
|
-
sessionId:
|
|
43
|
+
sessionId: extractCodexSessionId(json) ?? headerSessionId(headers),
|
|
38
44
|
};
|
|
39
45
|
}
|
|
40
46
|
return null;
|
package/src/routes/__root.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
|
|
3
|
-
import type
|
|
3
|
+
import { useEffect, type ReactNode } from "react";
|
|
4
4
|
import { SWRConfig } from "swr";
|
|
5
5
|
import faviconSvg from "../assets/favicon.svg?url";
|
|
6
6
|
import appCss from "../index.css?url";
|
|
7
|
+
import { installChunkLoadRecovery } from "../lib/chunkLoadRecovery";
|
|
7
8
|
|
|
8
9
|
export const Route = createRootRoute({
|
|
9
10
|
head: () => ({
|
|
@@ -22,6 +23,8 @@ export const Route = createRootRoute({
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
function RootComponent() {
|
|
26
|
+
useEffect(() => installChunkLoadRecovery(), []);
|
|
27
|
+
|
|
25
28
|
return (
|
|
26
29
|
<RootDocument>
|
|
27
30
|
<Outlet />
|
|
@@ -57,11 +60,11 @@ function RootNotFoundComponent() {
|
|
|
57
60
|
|
|
58
61
|
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
59
62
|
return (
|
|
60
|
-
<html lang="en" className="dark">
|
|
63
|
+
<html lang="en" className="dark" suppressHydrationWarning={true}>
|
|
61
64
|
<head>
|
|
62
65
|
<HeadContent />
|
|
63
66
|
</head>
|
|
64
|
-
<body>
|
|
67
|
+
<body suppressHydrationWarning={true}>
|
|
65
68
|
<SWRConfig value={{ revalidateOnFocus: false, revalidateIfStale: false }}>
|
|
66
69
|
{children}
|
|
67
70
|
</SWRConfig>
|
package/src/routes/index.tsx
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { useEffect, useState, type JSX } from "react";
|
|
2
3
|
import { ProxyViewerContainer } from "../components/ProxyViewerContainer";
|
|
3
4
|
|
|
4
5
|
export const Route = createFileRoute("/")({
|
|
5
|
-
|
|
6
|
+
ssr: false,
|
|
7
|
+
component: ClientOnlyProxyViewerRoute,
|
|
6
8
|
});
|
|
9
|
+
|
|
10
|
+
function ClientOnlyProxyViewerRoute(): JSX.Element {
|
|
11
|
+
const [mounted, setMounted] = useState(false);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setMounted(true);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
if (!mounted) {
|
|
18
|
+
return (
|
|
19
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
20
|
+
<div className="mx-auto flex min-h-screen w-full max-w-2xl flex-col justify-center px-6 py-16 text-center">
|
|
21
|
+
<div className="rounded-[8px] border border-border bg-card px-4 py-8 shadow-sm">
|
|
22
|
+
<p className="text-sm font-medium text-muted-foreground">Loading Inspector...</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</main>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <ProxyViewerContainer />;
|
|
30
|
+
}
|
|
@@ -9,6 +9,7 @@ import { decodeSessionIdFromPath, encodeSessionIdForPath } from "../../lib/sessi
|
|
|
9
9
|
* and Unicode project paths survive dev-server URL normalization.
|
|
10
10
|
*/
|
|
11
11
|
export const Route = createFileRoute("/session/$sessionId")({
|
|
12
|
+
ssr: false,
|
|
12
13
|
component: SessionViewerRoute,
|
|
13
14
|
parseParams: (params) => ({
|
|
14
15
|
sessionId: decodeSessionIdFromPath(params.sessionId),
|
package/styles/globals.css
CHANGED
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
--animate-crab-appear: crab-appear 300ms ease-out;
|
|
45
45
|
--animate-crab-settle: crab-settle 400ms ease-in-out;
|
|
46
46
|
--animate-crab-piano-pop: crab-piano-pop 400ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
|
|
47
|
+
--animate-crab-short-leg-body: crab-short-leg-body 720ms ease-in-out infinite;
|
|
48
|
+
--animate-crab-short-leg-step: crab-short-leg-step 360ms ease-in-out infinite;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
:root {
|
|
@@ -165,10 +167,42 @@
|
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
@keyframes crab-short-leg-body {
|
|
171
|
+
0%,
|
|
172
|
+
100% {
|
|
173
|
+
transform: translateY(0) rotate(-1deg);
|
|
174
|
+
}
|
|
175
|
+
25% {
|
|
176
|
+
transform: translateY(1px) rotate(1.5deg);
|
|
177
|
+
}
|
|
178
|
+
50% {
|
|
179
|
+
transform: translateY(-1px) rotate(-1.5deg);
|
|
180
|
+
}
|
|
181
|
+
75% {
|
|
182
|
+
transform: translateY(1px) rotate(1deg);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes crab-short-leg-step {
|
|
187
|
+
0%,
|
|
188
|
+
100% {
|
|
189
|
+
transform: translateY(0) scaleY(0.72) rotate(var(--crab-leg-rest, 0deg));
|
|
190
|
+
opacity: 0.42;
|
|
191
|
+
}
|
|
192
|
+
50% {
|
|
193
|
+
transform: translateY(3px) scaleY(1.15) rotate(var(--crab-leg-step, 0deg));
|
|
194
|
+
opacity: 0.9;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
168
198
|
@media (prefers-reduced-motion: reduce) {
|
|
169
199
|
.animate-crab-piano-pop {
|
|
170
200
|
animation: none !important;
|
|
171
201
|
}
|
|
202
|
+
.floating-crab-body,
|
|
203
|
+
.floating-crab-leg {
|
|
204
|
+
animation: none !important;
|
|
205
|
+
}
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
[data-deep-link-highlight="true"] {
|
|
@@ -205,6 +239,26 @@
|
|
|
205
239
|
}
|
|
206
240
|
|
|
207
241
|
@layer utilities {
|
|
242
|
+
.floating-crab-body {
|
|
243
|
+
animation: var(--animate-crab-short-leg-body);
|
|
244
|
+
transform-origin: 50% 74%;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.floating-crab-leg {
|
|
248
|
+
animation: var(--animate-crab-short-leg-step);
|
|
249
|
+
transform-origin: 50% 0%;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.floating-crab-leg-left {
|
|
253
|
+
--crab-leg-rest: -10deg;
|
|
254
|
+
--crab-leg-step: 14deg;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.floating-crab-leg-right {
|
|
258
|
+
--crab-leg-rest: 10deg;
|
|
259
|
+
--crab-leg-step: -14deg;
|
|
260
|
+
}
|
|
261
|
+
|
|
208
262
|
.inspector-scrollbar {
|
|
209
263
|
scrollbar-color: color-mix(in srgb, var(--muted-foreground) 32%, transparent) transparent;
|
|
210
264
|
scrollbar-width: thin;
|