@tonyclaw/agent-inspector 3.1.32 → 4.0.2
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/backend/nitro.json +1 -1
- package/.output/cli.js +241 -120
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-oXUk3MBR.mjs → router-BBY_zWRK.mjs} +91 -3
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-BtfPSpkV.js → CompareDrawer-rtNElN-B.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D9DPPQ3n.js → InspectorPet-QaAiuF3d.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Buu8vkCK.js → ProxyViewerContainer-6twNZHwl.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-BER-QzpD.js → ReplayDialog-B7KlziO2.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-C8Zwxrr7.js → RequestAnatomy-2XVFBrW4.js} +1 -1
- package/.output/ui/assets/{ResponseView-DDpKdYk6.js → ResponseView-BULgzLVB.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-sMPGG7Sw.js → StreamingChunkSequence-DSkvkbns.js} +1 -1
- package/.output/ui/assets/{_sessionId-e4IheHnP.js → _sessionId-D2dmJi3G.js} +1 -1
- package/.output/ui/assets/{_sessionId-C5Uv72Fx.js → _sessionId-DcS0s1Mr.js} +1 -1
- package/.output/ui/assets/{index-B__qT0Yd.js → index-B0TN1KKO.js} +2 -2
- package/.output/ui/assets/{index-Be3bTCzi.js → index-Bva1zHEs.js} +1 -1
- package/.output/ui/assets/{index-C9q__p8J.js → index-BzBExOxu.js} +1 -1
- package/.output/ui/assets/{index-CtB0deSZ.js → index-k6sRQz55.js} +1 -1
- package/.output/ui/assets/{json-viewer-Db0AFp-7.js → json-viewer-CWM4tY-j.js} +1 -1
- package/.output/ui/assets/{jszip.min-DkY0Ym_q.js → jszip.min-BkWvRFlo.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/package.json +8 -13
- package/src/assets/IDE/claudecode-color.svg +0 -1
- package/src/assets/IDE/codex-color.svg +0 -1
- package/src/assets/IDE/opencode.svg +0 -1
- package/src/assets/IDE/xiaomimimo.svg +0 -1
- package/src/assets/favicon.svg +0 -21
- package/src/assets/logos/alibaba.svg +0 -1
- package/src/assets/logos/anthropic.svg +0 -1
- package/src/assets/logos/claude-code.svg +0 -4
- package/src/assets/logos/deepseek.svg +0 -1
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +0 -1
- package/src/assets/logos/opencode.svg +0 -4
- package/src/assets/logos/qwen.webp +0 -0
- package/src/assets/logos/zhipuai.svg +0 -219
- package/src/backend/router.tsx +0 -36
- package/src/backend/routes/$.ts +0 -26
- package/src/backend/routes/__root.ts +0 -4
- package/src/backend/routes/api/-instances.ts +0 -7
- package/src/backend/routes/api/alerts.summary.ts +0 -28
- package/src/backend/routes/api/alerts.ts +0 -70
- package/src/backend/routes/api/config.paths.ts +0 -14
- package/src/backend/routes/api/config.ts +0 -73
- package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.ts +0 -21
- package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
- package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
- package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
- package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
- package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
- package/src/backend/routes/api/groups.$groupId.ts +0 -93
- package/src/backend/routes/api/groups.ts +0 -79
- package/src/backend/routes/api/health.ts +0 -22
- package/src/backend/routes/api/instances.$name.connection.ts +0 -12
- package/src/backend/routes/api/instances.$name.restart.ts +0 -23
- package/src/backend/routes/api/instances.$name.start.ts +0 -18
- package/src/backend/routes/api/instances.$name.stop.ts +0 -23
- package/src/backend/routes/api/instances.$name.ts +0 -12
- package/src/backend/routes/api/instances.ts +0 -16
- package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
- package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
- package/src/backend/routes/api/knowledge.candidates.ts +0 -10
- package/src/backend/routes/api/knowledge.project-context.ts +0 -29
- package/src/backend/routes/api/knowledge.search.ts +0 -62
- package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
- package/src/backend/routes/api/logs.$id.body.ts +0 -58
- package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
- package/src/backend/routes/api/logs.$id.replay.ts +0 -297
- package/src/backend/routes/api/logs.$id.ts +0 -23
- package/src/backend/routes/api/logs.import.ts +0 -39
- package/src/backend/routes/api/logs.stream.ts +0 -174
- package/src/backend/routes/api/logs.ts +0 -214
- package/src/backend/routes/api/mcp.ts +0 -25
- package/src/backend/routes/api/models.ts +0 -10
- package/src/backend/routes/api/pi-agent.ts +0 -29
- package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
- package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
- package/src/backend/routes/api/providers.$providerId.ts +0 -111
- package/src/backend/routes/api/providers.export.ts +0 -44
- package/src/backend/routes/api/providers.import.ts +0 -81
- package/src/backend/routes/api/providers.scan.ts +0 -28
- package/src/backend/routes/api/providers.ts +0 -102
- package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
- package/src/backend/routes/api/runs.$runId.ts +0 -50
- package/src/backend/routes/api/runs.ts +0 -62
- package/src/backend/routes/api/runtime.ts +0 -46
- package/src/backend/routes/api/sessions.ts +0 -51
- package/src/backend/routes/api/storage.ts +0 -56
- package/src/backend/routes/livez.ts +0 -13
- package/src/backend/routes/metrics.ts +0 -14
- package/src/backend/routes/proxy/$.ts +0 -15
- package/src/backend/routes/readyz.ts +0 -18
- package/src/backend/start.ts +0 -10
- package/src/cli/alias.ts +0 -431
- package/src/cli/detect-tools.ts +0 -123
- package/src/cli/doctor.ts +0 -836
- package/src/cli/instance.ts +0 -1071
- package/src/cli/instanceApi.ts +0 -213
- package/src/cli/instanceArgs.ts +0 -301
- package/src/cli/instanceControl.ts +0 -191
- package/src/cli/instanceModel.ts +0 -276
- package/src/cli/instanceRegistry.ts +0 -731
- package/src/cli/networkHints.ts +0 -170
- package/src/cli/onboard.ts +0 -1668
- package/src/cli/onboardTarget.ts +0 -228
- package/src/cli/rustBackendPackage.ts +0 -458
- package/src/cli/startupOutput.ts +0 -16
- package/src/cli/templates/codex-skill-onboard.ts +0 -320
- package/src/cli/templates/command-onboard.ts +0 -25
- package/src/cli/templates/skill-onboard.ts +0 -625
- package/src/cli.ts +0 -1397
- package/src/components/OnboardingBanner.tsx +0 -115
- package/src/components/ProxyViewer.tsx +0 -2259
- package/src/components/ProxyViewerContainer.tsx +0 -1076
- package/src/components/alerts/AlertsDialog.tsx +0 -615
- package/src/components/clients/ClientLogo.tsx +0 -175
- package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
- package/src/components/errors/SafeErrorBoundary.tsx +0 -201
- package/src/components/groups/GroupsDialog.tsx +0 -1304
- package/src/components/inspector-pet/InspectorPet.tsx +0 -398
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
- package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
- package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
- package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
- package/src/components/providers/ImportWizardDialog.tsx +0 -281
- package/src/components/providers/ProviderCard.tsx +0 -347
- package/src/components/providers/ProviderForm.tsx +0 -1183
- package/src/components/providers/ProviderLogo.tsx +0 -122
- package/src/components/providers/ProviderTestResultsView.tsx +0 -734
- package/src/components/providers/ProvidersPanel.tsx +0 -714
- package/src/components/providers/SettingsDialog.tsx +0 -1042
- package/src/components/providers/providerFormCapabilities.ts +0 -36
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
- package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
- package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
- package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
- package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
- package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
- package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
- package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
- package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
- package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
- package/src/components/proxy-viewer/ResponseView.tsx +0 -202
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
- package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
- package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
- package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
- package/src/components/proxy-viewer/accessibility.ts +0 -8
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
- package/src/components/proxy-viewer/anatomy/types.ts +0 -41
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
- package/src/components/proxy-viewer/bodyHydration.ts +0 -123
- package/src/components/proxy-viewer/cacheTrend.ts +0 -50
- package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
- package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
- package/src/components/proxy-viewer/diff/index.ts +0 -3
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
- package/src/components/proxy-viewer/formats/index.tsx +0 -47
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
- package/src/components/proxy-viewer/index.ts +0 -9
- package/src/components/proxy-viewer/lazy.ts +0 -41
- package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
- package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
- package/src/components/proxy-viewer/log-formats/index.ts +0 -23
- package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
- package/src/components/proxy-viewer/log-formats/types.ts +0 -40
- package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
- package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
- package/src/components/proxy-viewer/logFocus.ts +0 -54
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
- package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
- package/src/components/proxy-viewer/replayComparison.ts +0 -131
- package/src/components/proxy-viewer/requestDiff.ts +0 -277
- package/src/components/proxy-viewer/requestTools.ts +0 -220
- package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
- package/src/components/proxy-viewer/viewerState.ts +0 -176
- package/src/components/ui/badge.tsx +0 -47
- package/src/components/ui/button.tsx +0 -47
- package/src/components/ui/collapsible.tsx +0 -21
- package/src/components/ui/confirm-dialog.tsx +0 -51
- package/src/components/ui/crab-logo.tsx +0 -45
- package/src/components/ui/crab-variants.tsx +0 -467
- package/src/components/ui/dialog.tsx +0 -130
- package/src/components/ui/icon-trigger.ts +0 -2
- package/src/components/ui/json-viewer-bulk.ts +0 -132
- package/src/components/ui/json-viewer.tsx +0 -508
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/select.tsx +0 -178
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -88
- package/src/components/ui/tooltip.tsx +0 -51
- package/src/components/ui/transient-toast.tsx +0 -105
- package/src/contracts/anthropic.ts +0 -230
- package/src/contracts/index.ts +0 -59
- package/src/contracts/json.ts +0 -37
- package/src/contracts/log.ts +0 -139
- package/src/contracts/openai.ts +0 -267
- package/src/index.css +0 -11
- package/src/knowledge/candidateStore.ts +0 -94
- package/src/knowledge/distiller.ts +0 -98
- package/src/knowledge/openclawClient.ts +0 -161
- package/src/knowledge/openclawGatewayClient.ts +0 -249
- package/src/knowledge/openclawMarkdown.ts +0 -146
- package/src/knowledge/redactor.ts +0 -80
- package/src/knowledge/types.ts +0 -83
- package/src/lib/alertContract.ts +0 -76
- package/src/lib/apiClient.ts +0 -166
- package/src/lib/apiFormat.ts +0 -29
- package/src/lib/backendImplementation.ts +0 -16
- package/src/lib/basePath.ts +0 -107
- package/src/lib/browserRuntimeContract.ts +0 -124
- package/src/lib/browserRuntimeResolver.ts +0 -333
- package/src/lib/chunkLoadRecovery.ts +0 -83
- package/src/lib/claudeCodeStrip.ts +0 -98
- package/src/lib/clipboard.ts +0 -67
- package/src/lib/codingAgentEndpoints.ts +0 -54
- package/src/lib/ecosystemContract.ts +0 -130
- package/src/lib/export-logs.ts +0 -287
- package/src/lib/groupContract.ts +0 -166
- package/src/lib/instanceContract.ts +0 -239
- package/src/lib/jsonFile.ts +0 -46
- package/src/lib/logImportContract.ts +0 -12
- package/src/lib/managedInstance.ts +0 -112
- package/src/lib/mask.ts +0 -4
- package/src/lib/objectUtils.ts +0 -22
- package/src/lib/piAgentContract.ts +0 -102
- package/src/lib/providerContract.ts +0 -125
- package/src/lib/providerImportContract.ts +0 -27
- package/src/lib/providerModelMetadata.ts +0 -458
- package/src/lib/providerTestContract.ts +0 -170
- package/src/lib/providerTestPrompt.ts +0 -100
- package/src/lib/publicBasePathContext.ts +0 -137
- package/src/lib/resourceLimits.ts +0 -345
- package/src/lib/runContract.ts +0 -164
- package/src/lib/runtimeConfig.ts +0 -43
- package/src/lib/safeDiagnostic.ts +0 -38
- package/src/lib/safeFetch.ts +0 -508
- package/src/lib/serverPort.ts +0 -47
- package/src/lib/sessionInfoContract.ts +0 -78
- package/src/lib/sessionUrl.ts +0 -46
- package/src/lib/ssrfGuard.ts +0 -133
- package/src/lib/stopReason.ts +0 -130
- package/src/lib/themeMode.ts +0 -41
- package/src/lib/timeDisplay.ts +0 -22
- package/src/lib/toolTrace.ts +0 -290
- package/src/lib/uiScale.ts +0 -117
- package/src/lib/unknown.ts +0 -27
- package/src/lib/upstreamUrl.ts +0 -150
- package/src/lib/useAlerts.ts +0 -82
- package/src/lib/useGroupEvidence.ts +0 -37
- package/src/lib/useGroups.ts +0 -32
- package/src/lib/useOnboarding.ts +0 -93
- package/src/lib/useProviders.ts +0 -30
- package/src/lib/useStripConfig.ts +0 -154
- package/src/lib/useThemeMode.ts +0 -40
- package/src/lib/useUiScale.ts +0 -52
- package/src/lib/utils.ts +0 -78
- package/src/mcp/currentContext.ts +0 -65
- package/src/mcp/instanceHandlers.ts +0 -151
- package/src/mcp/loopback.ts +0 -88
- package/src/mcp/mode.ts +0 -75
- package/src/mcp/previewExtractor.ts +0 -233
- package/src/mcp/server.ts +0 -2274
- package/src/mcp/toolHandlers.ts +0 -1533
- package/src/proxy/alerts.ts +0 -664
- package/src/proxy/chunkStorage.ts +0 -123
- package/src/proxy/config.ts +0 -218
- package/src/proxy/constants.ts +0 -66
- package/src/proxy/dataDir.ts +0 -147
- package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
- package/src/proxy/ecosystemExecutionRoute.ts +0 -116
- package/src/proxy/ecosystemRegistry.ts +0 -296
- package/src/proxy/ecosystemTasks.ts +0 -415
- package/src/proxy/evidenceAnalysis.ts +0 -526
- package/src/proxy/evidenceExporter.ts +0 -235
- package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
- package/src/proxy/formats/anthropic/handler.ts +0 -75
- package/src/proxy/formats/anthropic/index.ts +0 -13
- package/src/proxy/formats/anthropic/register.ts +0 -4
- package/src/proxy/formats/anthropic/schemas.ts +0 -6
- package/src/proxy/formats/anthropic/stream.ts +0 -205
- package/src/proxy/formats/handler.ts +0 -46
- package/src/proxy/formats/index.ts +0 -13
- package/src/proxy/formats/jsonSchema.ts +0 -2
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
- package/src/proxy/formats/openai/codexMetadata.ts +0 -61
- package/src/proxy/formats/openai/handler.ts +0 -133
- package/src/proxy/formats/openai/index.ts +0 -33
- package/src/proxy/formats/openai/provider.ts +0 -50
- package/src/proxy/formats/openai/register.ts +0 -4
- package/src/proxy/formats/openai/schemas.ts +0 -25
- package/src/proxy/formats/openai/stream.ts +0 -600
- package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
- package/src/proxy/formats/providerAdapter.ts +0 -50
- package/src/proxy/formats/providerRegistry.ts +0 -51
- package/src/proxy/formats/providers/index.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -65
- package/src/proxy/groupEvidenceExporter.ts +0 -371
- package/src/proxy/groupStore.ts +0 -336
- package/src/proxy/handler.ts +0 -729
- package/src/proxy/identityProxy.ts +0 -1842
- package/src/proxy/jsonlRecovery.ts +0 -554
- package/src/proxy/logBodyChunks.ts +0 -91
- package/src/proxy/logFinalizer.ts +0 -777
- package/src/proxy/logFinalizer.worker.ts +0 -24
- package/src/proxy/logImportUpload.ts +0 -128
- package/src/proxy/logImporter.ts +0 -813
- package/src/proxy/logIndex.ts +0 -663
- package/src/proxy/logSearch.ts +0 -122
- package/src/proxy/logger.ts +0 -537
- package/src/proxy/openaiOrphanToolStrip.ts +0 -142
- package/src/proxy/platformCommands.ts +0 -253
- package/src/proxy/privateDataPath.ts +0 -419
- package/src/proxy/providerImporters.ts +0 -578
- package/src/proxy/providerScanStore.ts +0 -87
- package/src/proxy/providerSecretStore.ts +0 -216
- package/src/proxy/providers.ts +0 -998
- package/src/proxy/rawStreamCapture.ts +0 -342
- package/src/proxy/runFailures.ts +0 -94
- package/src/proxy/runStore.ts +0 -214
- package/src/proxy/runtimeAdmission.ts +0 -55
- package/src/proxy/runtimeHealth.ts +0 -221
- package/src/proxy/runtimeMetrics.ts +0 -149
- package/src/proxy/runtimeShutdown.ts +0 -157
- package/src/proxy/schemas.ts +0 -112
- package/src/proxy/sessionArchive.ts +0 -479
- package/src/proxy/sessionInfo.ts +0 -266
- package/src/proxy/sessionProcess.ts +0 -194
- package/src/proxy/sessionRuntime.ts +0 -121
- package/src/proxy/sessionSupervisor.ts +0 -305
- package/src/proxy/sessionWorkerEntry.ts +0 -26
- package/src/proxy/shutdownCoordinator.ts +0 -90
- package/src/proxy/socketTracker.ts +0 -639
- package/src/proxy/sqliteLogIndex.ts +0 -733
- package/src/proxy/storageLifecycle.ts +0 -432
- package/src/proxy/store.ts +0 -1773
- package/src/proxy/streamTiming.ts +0 -31
- package/src/proxy/toolSchemaWarnings.ts +0 -529
- package/src/proxy/uiAssetServer.ts +0 -106
- package/src/proxy/upstream.ts +0 -127
- package/src/services/alerts.ts +0 -12
- package/src/services/groups.ts +0 -119
- package/src/services/piAgent.ts +0 -421
- package/src/services/piAgentRetrieval.ts +0 -1180
- package/src/services/result.ts +0 -17
- package/src/services/runs.ts +0 -76
- package/src/ui/bootstrap.ts +0 -167
- package/src/ui/index.html +0 -38
- package/src/ui/logsStreamClient.ts +0 -20
- package/src/ui/main.tsx +0 -13
- package/src/ui/public/runtime-config.js +0 -2
- package/src/ui/router.tsx +0 -13
- package/src/ui/routes/__root.tsx +0 -69
- package/src/ui/routes/index.tsx +0 -35
- package/src/ui/routes/session/$sessionId.tsx +0 -50
- package/src/ui/runtimeDiscovery.ts +0 -50
|
@@ -1,1419 +0,0 @@
|
|
|
1
|
-
import { AlertTriangle, GitCompareArrows, Loader2, RefreshCw } from "lucide-react";
|
|
2
|
-
import { Suspense, type JSX, type ReactNode } from "react";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
|
-
import {
|
|
5
|
-
CapturedLogSchema,
|
|
6
|
-
LogBodyChunkSchema,
|
|
7
|
-
type CapturedLog,
|
|
8
|
-
type LogBodyPart,
|
|
9
|
-
} from "../../contracts";
|
|
10
|
-
import { ApiTimeoutError, fetchJsonWithTimeout } from "../../lib/apiClient";
|
|
11
|
-
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
12
|
-
import { cn } from "../../lib/utils";
|
|
13
|
-
import { LazyFeatureBoundary } from "../errors/SafeErrorBoundary";
|
|
14
|
-
import { stripClaudeCodeBillingHeader } from "../../lib/claudeCodeStrip";
|
|
15
|
-
import { Button } from "../ui/button";
|
|
16
|
-
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
17
|
-
import { useJsonBulkExpansion } from "../ui/json-viewer-bulk";
|
|
18
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
19
|
-
import { useCopyFeedback } from "./useCopyFeedback";
|
|
20
|
-
import type { HeaderTabActions } from "./LogEntryHeader";
|
|
21
|
-
import {
|
|
22
|
-
LazyJsonViewer,
|
|
23
|
-
LazyJsonViewerFromString,
|
|
24
|
-
LazyReplayDialog,
|
|
25
|
-
LazyRequestAnatomy,
|
|
26
|
-
LazyResponseView,
|
|
27
|
-
LazyStreamingChunkSequence,
|
|
28
|
-
} from "./lazy";
|
|
29
|
-
import type { AnatomySegment } from "./anatomy/types";
|
|
30
|
-
import { useAnatomyJump } from "./anatomy/useAnatomyJump";
|
|
31
|
-
import {
|
|
32
|
-
deleteHydratedLogFromCache,
|
|
33
|
-
describeFullBodyHydrationError,
|
|
34
|
-
formatBodyHydrationFootprint,
|
|
35
|
-
formatBytes,
|
|
36
|
-
getOrFetchHydratedLog,
|
|
37
|
-
readHydratedLogFromCache,
|
|
38
|
-
type FullBodyHydrationError,
|
|
39
|
-
} from "./bodyHydration";
|
|
40
|
-
import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
|
|
41
|
-
import { LogEntryHeader } from "./LogEntryHeader";
|
|
42
|
-
import { RequestTools } from "./RequestToolsPanel";
|
|
43
|
-
import type { CacheTrendEntry } from "./cacheTrend";
|
|
44
|
-
import { getLogFormatAdapter, resolveLogFormat } from "./log-formats";
|
|
45
|
-
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest, type LogFocusTab } from "./logFocus";
|
|
46
|
-
import type { ToolTraceEvent } from "./viewerState";
|
|
47
|
-
import {
|
|
48
|
-
shouldShowHeadersDiffButton,
|
|
49
|
-
shouldShowHeadersTab,
|
|
50
|
-
shouldShowRawHeadersTab,
|
|
51
|
-
shouldShowRawRequestTab,
|
|
52
|
-
shouldShowRequestDiffButton,
|
|
53
|
-
shouldShowSseTab,
|
|
54
|
-
} from "./logEntryVisibility";
|
|
55
|
-
import { parseRequestTools } from "./requestTools";
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Lightweight fallback for lazy-loaded tabs. Renders an empty 1px-tall row so
|
|
59
|
-
* the layout doesn't jump while the chunk is fetching.
|
|
60
|
-
*/
|
|
61
|
-
function TabFallback(): JSX.Element {
|
|
62
|
-
return <div className="h-1" aria-hidden="true" />;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function resolveFocusedTab(tab: LogFocusTab, anatomySegments: AnatomySegment[] | null): string {
|
|
66
|
-
switch (tab) {
|
|
67
|
-
case "anatomy":
|
|
68
|
-
return anatomySegments === null ? "request" : "anatomy";
|
|
69
|
-
case "request":
|
|
70
|
-
return "request";
|
|
71
|
-
case "response":
|
|
72
|
-
return "parsed";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
type BodyHydrationStatus = "idle" | "loading" | "failed";
|
|
77
|
-
type BodyChunkState = "idle" | "loading" | "success" | "failed";
|
|
78
|
-
|
|
79
|
-
type BodyPreviewState = {
|
|
80
|
-
status: BodyChunkState;
|
|
81
|
-
text: string;
|
|
82
|
-
offset: number;
|
|
83
|
-
totalBytes: number | null;
|
|
84
|
-
hasMore: boolean;
|
|
85
|
-
error: string | null;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const FULL_LOG_HYDRATION_TIMEOUT_MS = 15_000;
|
|
89
|
-
const BODY_CHUNK_TIMEOUT_MS = 15_000;
|
|
90
|
-
const BODY_CHUNK_BYTES = 256 * 1024;
|
|
91
|
-
const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
|
|
92
|
-
const COLLAPSED_BODY_ANALYSIS_THRESHOLD_BYTES = 256 * 1024;
|
|
93
|
-
const RAW_TEXT_FAST_PREVIEW_CHARS = 512 * 1024;
|
|
94
|
-
|
|
95
|
-
const TAB_TRIGGER_CLASS =
|
|
96
|
-
"h-9 flex-none rounded-md border border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:bg-white/[0.045] hover:text-foreground data-[state=active]:bg-cyan-300/[0.08] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
|
|
97
|
-
|
|
98
|
-
function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
|
|
99
|
-
return {
|
|
100
|
-
status: "idle",
|
|
101
|
-
text: "",
|
|
102
|
-
offset: 0,
|
|
103
|
-
totalBytes,
|
|
104
|
-
hasMore: totalBytes !== null && totalBytes > 0,
|
|
105
|
-
error: null,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function bodyByteCount(value: number | null | undefined): number {
|
|
110
|
-
return value ?? 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function bodySizeForAnalysis(body: string | null, byteCount: number | null | undefined): number {
|
|
114
|
-
return byteCount ?? body?.length ?? 0;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isCompactBody(log: CapturedLog): boolean {
|
|
118
|
-
return log.bodyContentMode === "compact" || log.bodyContentMode === "truncated";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function shouldUseChunkedBodyLoading(log: CapturedLog): boolean {
|
|
122
|
-
if (!isCompactBody(log)) return false;
|
|
123
|
-
return (
|
|
124
|
-
bodyByteCount(log.rawRequestBodyBytes) > CHUNKED_BODY_THRESHOLD_BYTES ||
|
|
125
|
-
bodyByteCount(log.responseTextBytes) > CHUNKED_BODY_THRESHOLD_BYTES
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function shouldHydrateLogBody(log: CapturedLog): boolean {
|
|
130
|
-
return isCompactBody(log) && !shouldUseChunkedBodyLoading(log);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function shouldAnalyzeLogBodies(log: CapturedLog, expanded: boolean): boolean {
|
|
134
|
-
if (isCompactBody(log)) return false;
|
|
135
|
-
if (expanded) {
|
|
136
|
-
return (
|
|
137
|
-
bodySizeForAnalysis(log.rawRequestBody, log.rawRequestBodyBytes) <=
|
|
138
|
-
CHUNKED_BODY_THRESHOLD_BYTES &&
|
|
139
|
-
bodySizeForAnalysis(log.responseText, log.responseTextBytes) <= CHUNKED_BODY_THRESHOLD_BYTES
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return (
|
|
143
|
-
bodySizeForAnalysis(log.rawRequestBody, log.rawRequestBodyBytes) <=
|
|
144
|
-
COLLAPSED_BODY_ANALYSIS_THRESHOLD_BYTES &&
|
|
145
|
-
bodySizeForAnalysis(log.responseText, log.responseTextBytes) <=
|
|
146
|
-
COLLAPSED_BODY_ANALYSIS_THRESHOLD_BYTES
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function tabNeedsBodyHydration(tab: string): boolean {
|
|
151
|
-
switch (tab) {
|
|
152
|
-
case "request":
|
|
153
|
-
case "raw-request":
|
|
154
|
-
case "anatomy":
|
|
155
|
-
case "raw":
|
|
156
|
-
case "parsed":
|
|
157
|
-
return true;
|
|
158
|
-
case "headers":
|
|
159
|
-
case "raw-headers":
|
|
160
|
-
case "sse":
|
|
161
|
-
return false;
|
|
162
|
-
default:
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function tabNeedsParsedRequest(tab: string): boolean {
|
|
168
|
-
switch (tab) {
|
|
169
|
-
case "request":
|
|
170
|
-
case "anatomy":
|
|
171
|
-
return true;
|
|
172
|
-
case "headers":
|
|
173
|
-
case "raw-headers":
|
|
174
|
-
case "raw-request":
|
|
175
|
-
case "raw":
|
|
176
|
-
case "parsed":
|
|
177
|
-
case "sse":
|
|
178
|
-
return false;
|
|
179
|
-
default:
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function bodyPreviewErrorMessage(error: unknown): string {
|
|
185
|
-
if (error instanceof ApiTimeoutError) {
|
|
186
|
-
return "Body preview timed out. The log is still available on disk; try loading again.";
|
|
187
|
-
}
|
|
188
|
-
if (error instanceof Error) return error.message;
|
|
189
|
-
return "Body preview could not be loaded.";
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function FullBodyHydrationNotice({
|
|
193
|
-
status,
|
|
194
|
-
error,
|
|
195
|
-
footprint,
|
|
196
|
-
onRetry,
|
|
197
|
-
}: {
|
|
198
|
-
status: BodyHydrationStatus;
|
|
199
|
-
error: FullBodyHydrationError | null;
|
|
200
|
-
footprint: string | null;
|
|
201
|
-
onRetry: () => void;
|
|
202
|
-
}): JSX.Element | null {
|
|
203
|
-
switch (status) {
|
|
204
|
-
case "idle":
|
|
205
|
-
return null;
|
|
206
|
-
case "loading":
|
|
207
|
-
return (
|
|
208
|
-
<NoticeBlock tone="loading" title="Loading full bodies">
|
|
209
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
210
|
-
<span className="flex min-w-0 items-center gap-2">
|
|
211
|
-
<Loader2 className="size-3.5 shrink-0 animate-spin text-cyan-200" />
|
|
212
|
-
<span>Loading full request and response bodies from the log store.</span>
|
|
213
|
-
</span>
|
|
214
|
-
{footprint !== null && (
|
|
215
|
-
<span className="rounded border border-cyan-300/15 bg-cyan-300/[0.06] px-2 py-0.5 font-mono text-[11px] text-cyan-100">
|
|
216
|
-
{footprint}
|
|
217
|
-
</span>
|
|
218
|
-
)}
|
|
219
|
-
</div>
|
|
220
|
-
</NoticeBlock>
|
|
221
|
-
);
|
|
222
|
-
case "failed":
|
|
223
|
-
return (
|
|
224
|
-
<NoticeBlock tone="warning" title={error?.title ?? "Full bodies could not be loaded"}>
|
|
225
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
226
|
-
<span>{error?.detail ?? "Compact metadata is still available."}</span>
|
|
227
|
-
<Button
|
|
228
|
-
type="button"
|
|
229
|
-
variant="outline"
|
|
230
|
-
size="sm"
|
|
231
|
-
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 gap-1.5 text-xs text-amber-100"
|
|
232
|
-
onClick={onRetry}
|
|
233
|
-
>
|
|
234
|
-
<RefreshCw className="size-3" />
|
|
235
|
-
Retry
|
|
236
|
-
</Button>
|
|
237
|
-
</div>
|
|
238
|
-
</NoticeBlock>
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function bodyPreviewTextForView(enabled: boolean, state: BodyPreviewState): string | null {
|
|
244
|
-
if (!enabled) return null;
|
|
245
|
-
if (state.text !== "") return state.text;
|
|
246
|
-
if (state.status === "success") return "";
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function ExpandedPanel({
|
|
251
|
-
children,
|
|
252
|
-
className,
|
|
253
|
-
}: {
|
|
254
|
-
children: ReactNode;
|
|
255
|
-
className?: string;
|
|
256
|
-
}): JSX.Element {
|
|
257
|
-
return (
|
|
258
|
-
<div
|
|
259
|
-
className={cn(
|
|
260
|
-
"bg-black/[0.14] inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-[8px] p-3",
|
|
261
|
-
className,
|
|
262
|
-
)}
|
|
263
|
-
>
|
|
264
|
-
{children}
|
|
265
|
-
</div>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function EmptyState({ children }: { children: ReactNode }): JSX.Element {
|
|
270
|
-
return (
|
|
271
|
-
<div className="bg-white/[0.035] rounded-md px-3 py-5 text-center text-xs italic text-muted-foreground">
|
|
272
|
-
{children}
|
|
273
|
-
</div>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function RawTextBlock({ children }: { children: ReactNode }): JSX.Element {
|
|
278
|
-
const [showFullText, setShowFullText] = useState(false);
|
|
279
|
-
const text = typeof children === "string" ? children : null;
|
|
280
|
-
const shouldPreview = text !== null && text.length > RAW_TEXT_FAST_PREVIEW_CHARS;
|
|
281
|
-
const visibleChildren =
|
|
282
|
-
text !== null && shouldPreview && !showFullText
|
|
283
|
-
? text.slice(0, RAW_TEXT_FAST_PREVIEW_CHARS)
|
|
284
|
-
: children;
|
|
285
|
-
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
setShowFullText(false);
|
|
288
|
-
}, [text]);
|
|
289
|
-
|
|
290
|
-
return (
|
|
291
|
-
<div className="space-y-2">
|
|
292
|
-
{shouldPreview && !showFullText && (
|
|
293
|
-
<div className="rounded-md border border-cyan-300/20 bg-cyan-300/[0.06] px-3 py-2 text-xs text-muted-foreground">
|
|
294
|
-
Showing the first {RAW_TEXT_FAST_PREVIEW_CHARS.toLocaleString()} characters for fast
|
|
295
|
-
rendering. The full body is preserved and can still be copied from the tab action.
|
|
296
|
-
</div>
|
|
297
|
-
)}
|
|
298
|
-
<pre className="bg-black/[0.18] inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-[8px] p-3 font-mono text-xs leading-relaxed text-muted-foreground">
|
|
299
|
-
{visibleChildren}
|
|
300
|
-
</pre>
|
|
301
|
-
{shouldPreview && (
|
|
302
|
-
<button
|
|
303
|
-
type="button"
|
|
304
|
-
className="rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
305
|
-
onClick={() => setShowFullText((value) => !value)}
|
|
306
|
-
>
|
|
307
|
-
{showFullText
|
|
308
|
-
? "Collapse fast preview"
|
|
309
|
-
: `Render full body (${text.length.toLocaleString()} chars)`}
|
|
310
|
-
</button>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
type NoticeTone = "loading" | "warning" | "danger";
|
|
317
|
-
|
|
318
|
-
const NOTICE_TONE_CLASSES: Record<NoticeTone, string> = {
|
|
319
|
-
loading: "border-transparent bg-cyan-400/[0.06] text-cyan-100",
|
|
320
|
-
warning: "border-transparent bg-amber-400/[0.08] text-amber-100",
|
|
321
|
-
danger: "border-transparent bg-rose-400/[0.08] text-rose-100",
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
function NoticeBlock({
|
|
325
|
-
tone,
|
|
326
|
-
title,
|
|
327
|
-
children,
|
|
328
|
-
}: {
|
|
329
|
-
tone: NoticeTone;
|
|
330
|
-
title?: string;
|
|
331
|
-
children: ReactNode;
|
|
332
|
-
}): JSX.Element {
|
|
333
|
-
return (
|
|
334
|
-
<div
|
|
335
|
-
className={cn(
|
|
336
|
-
"mb-3 rounded-md border px-3 py-2 text-xs shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]",
|
|
337
|
-
NOTICE_TONE_CLASSES[tone],
|
|
338
|
-
)}
|
|
339
|
-
>
|
|
340
|
-
{title !== undefined && <div className="mb-1 font-semibold">{title}</div>}
|
|
341
|
-
<div className="leading-relaxed text-muted-foreground">{children}</div>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function BodyPreviewNotice({
|
|
347
|
-
label,
|
|
348
|
-
state,
|
|
349
|
-
expectedBytes,
|
|
350
|
-
onLoadMore,
|
|
351
|
-
}: {
|
|
352
|
-
label: string;
|
|
353
|
-
state: BodyPreviewState;
|
|
354
|
-
expectedBytes: number | null;
|
|
355
|
-
onLoadMore: () => void;
|
|
356
|
-
}): JSX.Element {
|
|
357
|
-
const totalBytes = state.totalBytes ?? expectedBytes;
|
|
358
|
-
const loadedLabel =
|
|
359
|
-
totalBytes === null
|
|
360
|
-
? formatBytes(state.offset)
|
|
361
|
-
: `${formatBytes(state.offset)} / ${formatBytes(totalBytes)}`;
|
|
362
|
-
const showLoadMore = state.status !== "loading" && state.hasMore;
|
|
363
|
-
const actionLabel = state.status === "failed" ? "Retry" : "Load more";
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
<NoticeBlock
|
|
367
|
-
tone={state.status === "failed" ? "warning" : "loading"}
|
|
368
|
-
title={`${label} body preview`}
|
|
369
|
-
>
|
|
370
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
371
|
-
<span>
|
|
372
|
-
{state.status === "idle" && totalBytes !== null
|
|
373
|
-
? `${label} body is ${formatBytes(totalBytes)}. Loading the first preview chunk.`
|
|
374
|
-
: `Loaded ${loadedLabel}.`}
|
|
375
|
-
{state.status === "failed" && state.error !== null && ` ${state.error}`}
|
|
376
|
-
</span>
|
|
377
|
-
{showLoadMore && (
|
|
378
|
-
<Button
|
|
379
|
-
type="button"
|
|
380
|
-
variant="outline"
|
|
381
|
-
size="sm"
|
|
382
|
-
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-cyan-100"
|
|
383
|
-
onClick={onLoadMore}
|
|
384
|
-
>
|
|
385
|
-
{actionLabel}
|
|
386
|
-
</Button>
|
|
387
|
-
)}
|
|
388
|
-
</div>
|
|
389
|
-
</NoticeBlock>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function SseInlineNotice({
|
|
394
|
-
error,
|
|
395
|
-
onOpenSse,
|
|
396
|
-
}: {
|
|
397
|
-
error: string | null | undefined;
|
|
398
|
-
onOpenSse?: () => void;
|
|
399
|
-
}): JSX.Element | null {
|
|
400
|
-
if (error === undefined || error === null) return null;
|
|
401
|
-
return (
|
|
402
|
-
<NoticeBlock tone="danger" title="SSE Error">
|
|
403
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
404
|
-
<span className="font-mono">{error}</span>
|
|
405
|
-
{onOpenSse !== undefined && (
|
|
406
|
-
<Button
|
|
407
|
-
type="button"
|
|
408
|
-
variant="outline"
|
|
409
|
-
size="sm"
|
|
410
|
-
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-rose-100"
|
|
411
|
-
onClick={onOpenSse}
|
|
412
|
-
>
|
|
413
|
-
View SSE
|
|
414
|
-
</Button>
|
|
415
|
-
)}
|
|
416
|
-
</div>
|
|
417
|
-
</NoticeBlock>
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function SseChunksNotice({ onOpenSse }: { onOpenSse: () => void }): JSX.Element {
|
|
422
|
-
return (
|
|
423
|
-
<NoticeBlock tone="loading" title="SSE chunks">
|
|
424
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
425
|
-
<span>Streaming chunks are available in the SSE tab.</span>
|
|
426
|
-
<Button
|
|
427
|
-
type="button"
|
|
428
|
-
variant="outline"
|
|
429
|
-
size="sm"
|
|
430
|
-
className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-cyan-100"
|
|
431
|
-
onClick={onOpenSse}
|
|
432
|
-
>
|
|
433
|
-
View SSE
|
|
434
|
-
</Button>
|
|
435
|
-
</div>
|
|
436
|
-
</NoticeBlock>
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function HeaderRows({
|
|
441
|
-
headers,
|
|
442
|
-
emptyLabel,
|
|
443
|
-
}: {
|
|
444
|
-
headers: Record<string, string> | undefined;
|
|
445
|
-
emptyLabel: string;
|
|
446
|
-
}): JSX.Element {
|
|
447
|
-
const entries =
|
|
448
|
-
headers === undefined
|
|
449
|
-
? []
|
|
450
|
-
: Object.entries(headers).sort(([left], [right]) => left.localeCompare(right));
|
|
451
|
-
|
|
452
|
-
if (entries.length === 0) return <EmptyState>{emptyLabel}</EmptyState>;
|
|
453
|
-
|
|
454
|
-
return (
|
|
455
|
-
<div className="bg-black/[0.14] overflow-hidden rounded-[8px] font-mono text-xs">
|
|
456
|
-
{entries.map(([key, value]) => (
|
|
457
|
-
<div
|
|
458
|
-
key={key}
|
|
459
|
-
className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/[0.055] last:border-b-0"
|
|
460
|
-
>
|
|
461
|
-
<div className="min-w-0 truncate bg-white/[0.035] px-2.5 py-2 font-semibold text-cyan-200">
|
|
462
|
-
{key}
|
|
463
|
-
</div>
|
|
464
|
-
<div className="min-w-0 truncate px-2.5 py-2 text-muted-foreground" title={value}>
|
|
465
|
-
{value}
|
|
466
|
-
</div>
|
|
467
|
-
</div>
|
|
468
|
-
))}
|
|
469
|
-
</div>
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export type LogEntryProps = {
|
|
474
|
-
log: CapturedLog;
|
|
475
|
-
/** Human-facing ordinal inside the current session. Internal lookups still use `log.id`. */
|
|
476
|
-
displayNumber: number;
|
|
477
|
-
viewMode?: "simple" | "full";
|
|
478
|
-
/** Live "strip Claude Code billing header" flag, sourced once at the viewer
|
|
479
|
-
* container. Hoisted out of `LogEntry` so a single SWR subscription serves
|
|
480
|
-
* the whole virtualized list (N logs == N subscriptions is the previous
|
|
481
|
-
* cost).
|
|
482
|
-
*/
|
|
483
|
-
strip: boolean;
|
|
484
|
-
/** Slow-response threshold in seconds. `0` disables the warning indicator. */
|
|
485
|
-
slowResponseThresholdSeconds: number;
|
|
486
|
-
/** Controls whether timestamps render as compact local time or full ISO strings. */
|
|
487
|
-
timeDisplayFormat: TimeDisplayFormat;
|
|
488
|
-
/**
|
|
489
|
-
* Per-log cache token trend, looked up in the viewer-level trend map.
|
|
490
|
-
* `null` (or absent) means the header should render with no arrows.
|
|
491
|
-
*/
|
|
492
|
-
cacheTrend?: CacheTrendEntry | null;
|
|
493
|
-
/** Callback to open CompareDrawer with this log and its immediate predecessor. */
|
|
494
|
-
onCompareWithPrevious?: (log: CapturedLog) => void;
|
|
495
|
-
/** Show client PID/project metadata on the log header row. */
|
|
496
|
-
showClientMetadata?: boolean;
|
|
497
|
-
/** Tools called by the response, rendered in the compact log header. */
|
|
498
|
-
toolEvents?: ToolTraceEvent[];
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
function DiffToggleButton({
|
|
502
|
-
active,
|
|
503
|
-
onClick,
|
|
504
|
-
}: {
|
|
505
|
-
active: boolean;
|
|
506
|
-
onClick: (e: React.MouseEvent) => void;
|
|
507
|
-
}): JSX.Element {
|
|
508
|
-
return (
|
|
509
|
-
<Tooltip>
|
|
510
|
-
<TooltipTrigger asChild>
|
|
511
|
-
<Button
|
|
512
|
-
variant={active ? "default" : "outline"}
|
|
513
|
-
size="sm"
|
|
514
|
-
className={cn(
|
|
515
|
-
"h-8 border-white/10 text-xs shadow-none",
|
|
516
|
-
active
|
|
517
|
-
? "bg-cyan-300 text-black hover:bg-cyan-200"
|
|
518
|
-
: "border border-input bg-background hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-cyan-100",
|
|
519
|
-
)}
|
|
520
|
-
onClick={onClick}
|
|
521
|
-
aria-pressed={active}
|
|
522
|
-
>
|
|
523
|
-
<GitCompareArrows className="size-3.5 mr-1" />
|
|
524
|
-
{active ? "Showing diff" : "Diff with Raw"}
|
|
525
|
-
</Button>
|
|
526
|
-
</TooltipTrigger>
|
|
527
|
-
<TooltipContent>
|
|
528
|
-
{active ? "Hide diff view" : "Compare proxy output against the original raw version"}
|
|
529
|
-
</TooltipContent>
|
|
530
|
-
</Tooltip>
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const RequestDiffContent = memo(function ({
|
|
535
|
-
rawBody,
|
|
536
|
-
displayedBody,
|
|
537
|
-
emptyLabel,
|
|
538
|
-
}: {
|
|
539
|
-
rawBody: string | null;
|
|
540
|
-
displayedBody: string | null;
|
|
541
|
-
emptyLabel: string;
|
|
542
|
-
}): JSX.Element {
|
|
543
|
-
const result = useMemo(
|
|
544
|
-
() => computeRequestDiff(rawBody, displayedBody),
|
|
545
|
-
[rawBody, displayedBody],
|
|
546
|
-
);
|
|
547
|
-
return <DiffView result={result} emptyLabel={emptyLabel} />;
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
const HeadersDiffContent = memo(function ({
|
|
551
|
-
rawHeaders,
|
|
552
|
-
headers,
|
|
553
|
-
emptyLabel,
|
|
554
|
-
}: {
|
|
555
|
-
rawHeaders: Record<string, string> | undefined;
|
|
556
|
-
headers: Record<string, string> | undefined;
|
|
557
|
-
emptyLabel: string;
|
|
558
|
-
}): JSX.Element {
|
|
559
|
-
const result = useMemo(() => computeHeadersDiff(rawHeaders, headers), [rawHeaders, headers]);
|
|
560
|
-
return <DiffView result={result} emptyLabel={emptyLabel} />;
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
export const LogEntry = memo(function ({
|
|
564
|
-
log,
|
|
565
|
-
displayNumber,
|
|
566
|
-
viewMode = "simple",
|
|
567
|
-
strip,
|
|
568
|
-
slowResponseThresholdSeconds,
|
|
569
|
-
timeDisplayFormat,
|
|
570
|
-
cacheTrend = null,
|
|
571
|
-
onCompareWithPrevious,
|
|
572
|
-
showClientMetadata = true,
|
|
573
|
-
toolEvents = [],
|
|
574
|
-
}: LogEntryProps): JSX.Element {
|
|
575
|
-
const [expanded, setExpanded] = useState<boolean>(false);
|
|
576
|
-
const [replayOpen, setReplayOpen] = useState<boolean>(false);
|
|
577
|
-
const [headersDiff, setHeadersDiff] = useState<boolean>(false);
|
|
578
|
-
const [requestDiff, setRequestDiff] = useState<boolean>(false);
|
|
579
|
-
const [activeTab, setActiveTab] = useState("request");
|
|
580
|
-
const [focusedToolIndex, setFocusedToolIndex] = useState<number | null>(null);
|
|
581
|
-
const [toolFocusNonce, setToolFocusNonce] = useState(0);
|
|
582
|
-
const [expandToPath, setExpandToPath] = useState<string | null>(null);
|
|
583
|
-
const [hydratedLog, setHydratedLog] = useState<CapturedLog | null>(null);
|
|
584
|
-
const [bodyHydrationStatus, setBodyHydrationStatus] = useState<BodyHydrationStatus>("idle");
|
|
585
|
-
const [bodyHydrationError, setBodyHydrationError] = useState<FullBodyHydrationError | null>(null);
|
|
586
|
-
const [bodyHydrationRetryNonce, setBodyHydrationRetryNonce] = useState(0);
|
|
587
|
-
const [requestPreview, setRequestPreview] = useState<BodyPreviewState>(() =>
|
|
588
|
-
createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null),
|
|
589
|
-
);
|
|
590
|
-
const [responsePreview, setResponsePreview] = useState<BodyPreviewState>(() =>
|
|
591
|
-
createEmptyBodyPreviewState(log.responseTextBytes ?? null),
|
|
592
|
-
);
|
|
593
|
-
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
594
|
-
const logIdRef = useRef(log.id);
|
|
595
|
-
const bodyHydrationInFlightRef = useRef(false);
|
|
596
|
-
const userSelectedTabRef = useRef(false);
|
|
597
|
-
const displayLog = hydratedLog ?? log;
|
|
598
|
-
const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
|
|
599
|
-
const fullBodyHydrationFootprint = useMemo(
|
|
600
|
-
() => formatBodyHydrationFootprint(log.rawRequestBodyBytes, log.responseTextBytes),
|
|
601
|
-
[log.rawRequestBodyBytes, log.responseTextBytes],
|
|
602
|
-
);
|
|
603
|
-
const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
|
|
604
|
-
const responsePreviewBody = bodyPreviewTextForView(useChunkedBody, responsePreview);
|
|
605
|
-
const rawRequestBodyForView = displayLog.rawRequestBody ?? requestPreviewBody;
|
|
606
|
-
const responseTextForView = displayLog.responseText ?? responsePreviewBody;
|
|
607
|
-
const requestPreviewIsActive = displayLog.rawRequestBody === null && requestPreviewBody !== null;
|
|
608
|
-
const responsePreviewIsActive = displayLog.responseText === null && responsePreviewBody !== null;
|
|
609
|
-
const requestBodyForViewCanParse =
|
|
610
|
-
bodySizeForAnalysis(rawRequestBodyForView, log.rawRequestBodyBytes) <=
|
|
611
|
-
CHUNKED_BODY_THRESHOLD_BYTES;
|
|
612
|
-
const responseBodyForViewCanParse =
|
|
613
|
-
bodySizeForAnalysis(responseTextForView, log.responseTextBytes) <= CHUNKED_BODY_THRESHOLD_BYTES;
|
|
614
|
-
const requestPreviewShouldParse =
|
|
615
|
-
requestBodyForViewCanParse &&
|
|
616
|
-
(!requestPreviewIsActive ||
|
|
617
|
-
(requestPreview.hasMore === false &&
|
|
618
|
-
bodyByteCount(requestPreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES));
|
|
619
|
-
const responsePreviewShouldParse =
|
|
620
|
-
responseBodyForViewCanParse &&
|
|
621
|
-
(!responsePreviewIsActive ||
|
|
622
|
-
(responsePreview.hasMore === false &&
|
|
623
|
-
bodyByteCount(responsePreview.totalBytes) <= CHUNKED_BODY_THRESHOLD_BYTES));
|
|
624
|
-
const resolvedFormat = resolveLogFormat(displayLog);
|
|
625
|
-
const adapter = getLogFormatAdapter(resolvedFormat);
|
|
626
|
-
const analyzeBodies = shouldAnalyzeLogBodies(displayLog, expanded);
|
|
627
|
-
const requestAnalysis = useMemo(
|
|
628
|
-
() => adapter.analyzeRequest(analyzeBodies ? displayLog.rawRequestBody : null),
|
|
629
|
-
[adapter, analyzeBodies, displayLog.rawRequestBody],
|
|
630
|
-
);
|
|
631
|
-
const responseAnalysis = useMemo(
|
|
632
|
-
() => adapter.analyzeResponse(analyzeBodies ? displayLog.responseText : null),
|
|
633
|
-
[adapter, analyzeBodies, displayLog.responseText],
|
|
634
|
-
);
|
|
635
|
-
const strippedRequestBody = useMemo(() => {
|
|
636
|
-
if (!strip || resolvedFormat !== "anthropic" || displayLog.rawRequestBody === null) {
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
return stripClaudeCodeBillingHeader(displayLog.rawRequestBody).body;
|
|
640
|
-
}, [displayLog.rawRequestBody, resolvedFormat, strip]);
|
|
641
|
-
const displayedRequestBody = strippedRequestBody ?? rawRequestBodyForView;
|
|
642
|
-
const requestExpansion = useJsonBulkExpansion(
|
|
643
|
-
expanded && tabNeedsParsedRequest(activeTab) && requestPreviewShouldParse
|
|
644
|
-
? displayedRequestBody
|
|
645
|
-
: null,
|
|
646
|
-
);
|
|
647
|
-
const rawRequestExpansion = useJsonBulkExpansion(
|
|
648
|
-
activeTab === "raw-request" && requestPreviewShouldParse ? rawRequestBodyForView : null,
|
|
649
|
-
);
|
|
650
|
-
const responseExpansion = useJsonBulkExpansion(
|
|
651
|
-
activeTab === "raw" && responsePreviewShouldParse ? responseTextForView : null,
|
|
652
|
-
);
|
|
653
|
-
const showSseTab = shouldShowSseTab(viewMode, displayLog.streaming);
|
|
654
|
-
const parsedRequestForSummary = requestExpansion.parsedData ?? requestAnalysis.parsed;
|
|
655
|
-
const calledToolNamesForView = useMemo(() => {
|
|
656
|
-
const analyzedToolNames = responseAnalysis.toolNames;
|
|
657
|
-
if (analyzedToolNames !== null) return analyzedToolNames;
|
|
658
|
-
return toolEvents.map((event) => event.name);
|
|
659
|
-
}, [responseAnalysis.toolNames, toolEvents]);
|
|
660
|
-
|
|
661
|
-
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
662
|
-
// Only build the string when there's at least one entry; empty headers would
|
|
663
|
-
// otherwise copy "{}", which is misleading.
|
|
664
|
-
const headersText = useMemo(
|
|
665
|
-
() =>
|
|
666
|
-
displayLog.headers && Object.keys(displayLog.headers).length > 0
|
|
667
|
-
? JSON.stringify(displayLog.headers, null, 2)
|
|
668
|
-
: null,
|
|
669
|
-
[displayLog.headers],
|
|
670
|
-
);
|
|
671
|
-
const rawHeadersText = useMemo(
|
|
672
|
-
() =>
|
|
673
|
-
displayLog.rawHeaders && Object.keys(displayLog.rawHeaders).length > 0
|
|
674
|
-
? JSON.stringify(displayLog.rawHeaders, null, 2)
|
|
675
|
-
: null,
|
|
676
|
-
[displayLog.rawHeaders],
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
// One copy-feedback hook per JSON-bearing tab, so each tab can surface its
|
|
680
|
-
// own Copy button in the header with independent "Copied!" feedback.
|
|
681
|
-
const requestCopy = useCopyFeedback(displayedRequestBody);
|
|
682
|
-
const rawRequestCopy = useCopyFeedback(rawRequestBodyForView);
|
|
683
|
-
const headersCopy = useCopyFeedback(headersText);
|
|
684
|
-
const rawHeadersCopy = useCopyFeedback(rawHeadersText);
|
|
685
|
-
const responseCopy = useCopyFeedback(responseTextForView);
|
|
686
|
-
|
|
687
|
-
// Per-tab action bundles consumed by the header. The header renders the
|
|
688
|
-
// entry whose key matches `activeTab`. Tabs without an entry (Context,
|
|
689
|
-
// Parsed Response) render no header buttons.
|
|
690
|
-
const tabActions: HeaderTabActions = useMemo(
|
|
691
|
-
() => ({
|
|
692
|
-
request: {
|
|
693
|
-
copyLabel: requestPreviewIsActive ? "Copy loaded request preview" : "Copy request body",
|
|
694
|
-
copyText: displayedRequestBody,
|
|
695
|
-
copyCopied: requestCopy.copied,
|
|
696
|
-
onCopy: requestCopy.copy,
|
|
697
|
-
expansion: requestPreviewShouldParse
|
|
698
|
-
? {
|
|
699
|
-
isExpanded: requestExpansion.isExpanded,
|
|
700
|
-
isPending: requestExpansion.isPending,
|
|
701
|
-
onToggle: requestExpansion.toggle,
|
|
702
|
-
}
|
|
703
|
-
: null,
|
|
704
|
-
// "Diff with Raw" only makes sense when there's a raw body to compare
|
|
705
|
-
// against (Anthropic + strip pipeline produces one). "Diff with
|
|
706
|
-
// Previous" only exists when the parent wired up the compare drawer.
|
|
707
|
-
diffWithRaw: shouldShowRequestDiffButton(
|
|
708
|
-
resolvedFormat,
|
|
709
|
-
viewMode,
|
|
710
|
-
strip,
|
|
711
|
-
displayLog.rawRequestBody !== null,
|
|
712
|
-
)
|
|
713
|
-
? { active: requestDiff, onToggle: () => setRequestDiff(!requestDiff) }
|
|
714
|
-
: undefined,
|
|
715
|
-
diffWithPrevious:
|
|
716
|
-
onCompareWithPrevious === undefined
|
|
717
|
-
? undefined
|
|
718
|
-
: () => {
|
|
719
|
-
onCompareWithPrevious(displayLog);
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
"raw-request": {
|
|
723
|
-
copyLabel: requestPreviewIsActive ? "Copy loaded raw request preview" : "Copy raw request",
|
|
724
|
-
copyText: rawRequestBodyForView,
|
|
725
|
-
copyCopied: rawRequestCopy.copied,
|
|
726
|
-
onCopy: rawRequestCopy.copy,
|
|
727
|
-
expansion: requestPreviewShouldParse
|
|
728
|
-
? {
|
|
729
|
-
isExpanded: rawRequestExpansion.isExpanded,
|
|
730
|
-
isPending: rawRequestExpansion.isPending,
|
|
731
|
-
onToggle: rawRequestExpansion.toggle,
|
|
732
|
-
}
|
|
733
|
-
: null,
|
|
734
|
-
},
|
|
735
|
-
// Tab-level actions mirror the visibility rules in logEntryVisibility:
|
|
736
|
-
// when the corresponding tab is hidden because the log has no captured
|
|
737
|
-
// headers, the header bar must not surface a Copy button either. We
|
|
738
|
-
// therefore omit the key entirely (header renders no actions) instead
|
|
739
|
-
// of returning a bundle with `copyText: null`.
|
|
740
|
-
...(shouldShowHeadersTab(
|
|
741
|
-
viewMode,
|
|
742
|
-
displayLog.headers !== undefined && Object.keys(displayLog.headers).length > 0,
|
|
743
|
-
)
|
|
744
|
-
? {
|
|
745
|
-
headers: {
|
|
746
|
-
copyLabel: "Copy headers",
|
|
747
|
-
copyText: headersText,
|
|
748
|
-
copyCopied: headersCopy.copied,
|
|
749
|
-
onCopy: headersCopy.copy,
|
|
750
|
-
// Headers are a flat dict, no JSON tree to expand.
|
|
751
|
-
expansion: null,
|
|
752
|
-
},
|
|
753
|
-
}
|
|
754
|
-
: {}),
|
|
755
|
-
...(shouldShowRawHeadersTab(
|
|
756
|
-
viewMode,
|
|
757
|
-
displayLog.rawHeaders !== undefined && Object.keys(displayLog.rawHeaders).length > 0,
|
|
758
|
-
)
|
|
759
|
-
? {
|
|
760
|
-
"raw-headers": {
|
|
761
|
-
copyLabel: "Copy raw headers",
|
|
762
|
-
copyText: rawHeadersText,
|
|
763
|
-
copyCopied: rawHeadersCopy.copied,
|
|
764
|
-
onCopy: rawHeadersCopy.copy,
|
|
765
|
-
expansion: null,
|
|
766
|
-
},
|
|
767
|
-
}
|
|
768
|
-
: {}),
|
|
769
|
-
raw: {
|
|
770
|
-
copyLabel: responsePreviewIsActive ? "Copy loaded response preview" : "Copy response",
|
|
771
|
-
copyText: responseTextForView,
|
|
772
|
-
copyCopied: responseCopy.copied,
|
|
773
|
-
onCopy: responseCopy.copy,
|
|
774
|
-
expansion: responsePreviewShouldParse
|
|
775
|
-
? {
|
|
776
|
-
isExpanded: responseExpansion.isExpanded,
|
|
777
|
-
isPending: responseExpansion.isPending,
|
|
778
|
-
onToggle: responseExpansion.toggle,
|
|
779
|
-
}
|
|
780
|
-
: null,
|
|
781
|
-
},
|
|
782
|
-
}),
|
|
783
|
-
[
|
|
784
|
-
displayedRequestBody,
|
|
785
|
-
requestPreviewIsActive,
|
|
786
|
-
requestPreviewShouldParse,
|
|
787
|
-
requestCopy,
|
|
788
|
-
requestExpansion,
|
|
789
|
-
requestDiff,
|
|
790
|
-
displayLog,
|
|
791
|
-
displayLog.rawRequestBody,
|
|
792
|
-
rawRequestBodyForView,
|
|
793
|
-
rawRequestCopy,
|
|
794
|
-
rawRequestExpansion,
|
|
795
|
-
headersText,
|
|
796
|
-
headersCopy,
|
|
797
|
-
rawHeadersText,
|
|
798
|
-
rawHeadersCopy,
|
|
799
|
-
displayLog.responseText,
|
|
800
|
-
responsePreviewIsActive,
|
|
801
|
-
responsePreviewShouldParse,
|
|
802
|
-
responseTextForView,
|
|
803
|
-
responseCopy,
|
|
804
|
-
responseExpansion,
|
|
805
|
-
resolvedFormat,
|
|
806
|
-
viewMode,
|
|
807
|
-
strip,
|
|
808
|
-
onCompareWithPrevious,
|
|
809
|
-
],
|
|
810
|
-
);
|
|
811
|
-
const anatomySegments = useMemo(
|
|
812
|
-
() =>
|
|
813
|
-
parsedRequestForSummary !== null ? adapter.anatomySegments(parsedRequestForSummary) : null,
|
|
814
|
-
[adapter, parsedRequestForSummary],
|
|
815
|
-
);
|
|
816
|
-
const requestTools = useMemo(
|
|
817
|
-
() => parseRequestTools(parsedRequestForSummary),
|
|
818
|
-
[parsedRequestForSummary],
|
|
819
|
-
);
|
|
820
|
-
const warnings = displayLog.warnings ?? [];
|
|
821
|
-
const anatomyPaths = useMemo(() => {
|
|
822
|
-
if (anatomySegments === null) return undefined;
|
|
823
|
-
return new Set(anatomySegments.map((s) => s.path));
|
|
824
|
-
}, [anatomySegments]);
|
|
825
|
-
const jumpToAnatomySegment = useAnatomyJump({
|
|
826
|
-
containerRef: requestJsonRef,
|
|
827
|
-
setExpandToPath,
|
|
828
|
-
ensureTabActive: () => {
|
|
829
|
-
if (activeTab !== "request") {
|
|
830
|
-
userSelectedTabRef.current = true;
|
|
831
|
-
setActiveTab("request");
|
|
832
|
-
}
|
|
833
|
-
},
|
|
834
|
-
});
|
|
835
|
-
const loadBodyChunk = useCallback(
|
|
836
|
-
(part: LogBodyPart, offset: number) => {
|
|
837
|
-
const setPreview = part === "request" ? setRequestPreview : setResponsePreview;
|
|
838
|
-
setPreview((current) => ({
|
|
839
|
-
...current,
|
|
840
|
-
status: "loading",
|
|
841
|
-
error: null,
|
|
842
|
-
}));
|
|
843
|
-
|
|
844
|
-
const params = new URLSearchParams({
|
|
845
|
-
part,
|
|
846
|
-
offset: String(offset),
|
|
847
|
-
limit: String(BODY_CHUNK_BYTES),
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
void fetchJsonWithTimeout(
|
|
851
|
-
`/api/logs/${String(log.id)}/body?${params.toString()}`,
|
|
852
|
-
LogBodyChunkSchema,
|
|
853
|
-
BODY_CHUNK_TIMEOUT_MS,
|
|
854
|
-
)
|
|
855
|
-
.then((chunk) => {
|
|
856
|
-
if (logIdRef.current !== log.id) return;
|
|
857
|
-
setPreview((current) => ({
|
|
858
|
-
status: "success",
|
|
859
|
-
text: chunk.offset === 0 ? chunk.text : `${current.text}${chunk.text}`,
|
|
860
|
-
offset: chunk.nextOffset ?? chunk.totalBytes,
|
|
861
|
-
totalBytes: chunk.totalBytes,
|
|
862
|
-
hasMore: chunk.hasMore,
|
|
863
|
-
error: null,
|
|
864
|
-
}));
|
|
865
|
-
})
|
|
866
|
-
.catch((error: unknown) => {
|
|
867
|
-
if (logIdRef.current !== log.id) return;
|
|
868
|
-
setPreview((current) => ({
|
|
869
|
-
...current,
|
|
870
|
-
status: "failed",
|
|
871
|
-
error: bodyPreviewErrorMessage(error),
|
|
872
|
-
}));
|
|
873
|
-
});
|
|
874
|
-
},
|
|
875
|
-
[log.id],
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
const retryFullBodyHydration = useCallback(() => {
|
|
879
|
-
if (bodyHydrationInFlightRef.current) return;
|
|
880
|
-
deleteHydratedLogFromCache(log.id);
|
|
881
|
-
setHydratedLog(null);
|
|
882
|
-
setBodyHydrationStatus("idle");
|
|
883
|
-
setBodyHydrationError(null);
|
|
884
|
-
setBodyHydrationRetryNonce((current) => current + 1);
|
|
885
|
-
}, [log.id]);
|
|
886
|
-
|
|
887
|
-
const handleToggleExpanded = useCallback(() => {
|
|
888
|
-
const nextExpanded = !expanded;
|
|
889
|
-
if (nextExpanded) {
|
|
890
|
-
userSelectedTabRef.current = false;
|
|
891
|
-
setActiveTab(anatomySegments !== null ? "anatomy" : "request");
|
|
892
|
-
}
|
|
893
|
-
setExpanded(nextExpanded);
|
|
894
|
-
}, [anatomySegments, expanded]);
|
|
895
|
-
|
|
896
|
-
const openSseTab = useCallback(() => {
|
|
897
|
-
if (!showSseTab) return;
|
|
898
|
-
userSelectedTabRef.current = true;
|
|
899
|
-
setActiveTab("sse");
|
|
900
|
-
}, [showSseTab]);
|
|
901
|
-
|
|
902
|
-
const handleTabChange = useCallback((value: string) => {
|
|
903
|
-
userSelectedTabRef.current = true;
|
|
904
|
-
setActiveTab(value);
|
|
905
|
-
}, []);
|
|
906
|
-
|
|
907
|
-
useEffect(() => {
|
|
908
|
-
logIdRef.current = log.id;
|
|
909
|
-
bodyHydrationInFlightRef.current = false;
|
|
910
|
-
setHydratedLog(null);
|
|
911
|
-
setBodyHydrationStatus("idle");
|
|
912
|
-
setBodyHydrationError(null);
|
|
913
|
-
setBodyHydrationRetryNonce(0);
|
|
914
|
-
userSelectedTabRef.current = false;
|
|
915
|
-
setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
|
|
916
|
-
setResponsePreview(createEmptyBodyPreviewState(log.responseTextBytes ?? null));
|
|
917
|
-
}, [log.id, log.rawRequestBodyBytes, log.responseTextBytes]);
|
|
918
|
-
|
|
919
|
-
useEffect(() => {
|
|
920
|
-
if (!expanded) return;
|
|
921
|
-
if (userSelectedTabRef.current) return;
|
|
922
|
-
if (activeTab !== "request") return;
|
|
923
|
-
if (anatomySegments === null) return;
|
|
924
|
-
setActiveTab("anatomy");
|
|
925
|
-
}, [activeTab, anatomySegments, expanded]);
|
|
926
|
-
|
|
927
|
-
useEffect(() => {
|
|
928
|
-
if (activeTab !== "sse" || showSseTab) return;
|
|
929
|
-
setActiveTab("parsed");
|
|
930
|
-
}, [activeTab, showSseTab]);
|
|
931
|
-
|
|
932
|
-
useEffect(() => {
|
|
933
|
-
if (!expanded) return;
|
|
934
|
-
if (!tabNeedsBodyHydration(activeTab)) return;
|
|
935
|
-
if (!shouldHydrateLogBody(log)) return;
|
|
936
|
-
if (hydratedLog !== null || bodyHydrationInFlightRef.current) return;
|
|
937
|
-
|
|
938
|
-
const cached = readHydratedLogFromCache(log.id);
|
|
939
|
-
if (cached !== null) {
|
|
940
|
-
setHydratedLog(cached);
|
|
941
|
-
setBodyHydrationStatus("idle");
|
|
942
|
-
setBodyHydrationError(null);
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
let cancelled = false;
|
|
947
|
-
bodyHydrationInFlightRef.current = true;
|
|
948
|
-
setBodyHydrationStatus("loading");
|
|
949
|
-
setBodyHydrationError(null);
|
|
950
|
-
void getOrFetchHydratedLog(log.id, () =>
|
|
951
|
-
fetchJsonWithTimeout(
|
|
952
|
-
`/api/logs/${String(log.id)}`,
|
|
953
|
-
CapturedLogSchema,
|
|
954
|
-
FULL_LOG_HYDRATION_TIMEOUT_MS,
|
|
955
|
-
),
|
|
956
|
-
)
|
|
957
|
-
.then((fullLog) => {
|
|
958
|
-
if (cancelled) return;
|
|
959
|
-
bodyHydrationInFlightRef.current = false;
|
|
960
|
-
setHydratedLog(fullLog);
|
|
961
|
-
setBodyHydrationStatus("idle");
|
|
962
|
-
setBodyHydrationError(null);
|
|
963
|
-
})
|
|
964
|
-
.catch((error: unknown) => {
|
|
965
|
-
if (cancelled) return;
|
|
966
|
-
bodyHydrationInFlightRef.current = false;
|
|
967
|
-
setBodyHydrationStatus("failed");
|
|
968
|
-
setBodyHydrationError(describeFullBodyHydrationError(error));
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
return () => {
|
|
972
|
-
cancelled = true;
|
|
973
|
-
bodyHydrationInFlightRef.current = false;
|
|
974
|
-
};
|
|
975
|
-
}, [
|
|
976
|
-
activeTab,
|
|
977
|
-
expanded,
|
|
978
|
-
hydratedLog,
|
|
979
|
-
log.bodyContentMode,
|
|
980
|
-
log.id,
|
|
981
|
-
log.rawRequestBodyBytes,
|
|
982
|
-
log.responseTextBytes,
|
|
983
|
-
bodyHydrationRetryNonce,
|
|
984
|
-
]);
|
|
985
|
-
|
|
986
|
-
useEffect(() => {
|
|
987
|
-
if (!expanded) return;
|
|
988
|
-
if (!useChunkedBody) return;
|
|
989
|
-
if (requestPreview.status !== "idle") return;
|
|
990
|
-
if (activeTab !== "request" && activeTab !== "raw-request" && activeTab !== "anatomy") return;
|
|
991
|
-
loadBodyChunk("request", 0);
|
|
992
|
-
}, [activeTab, expanded, loadBodyChunk, requestPreview.status, useChunkedBody]);
|
|
993
|
-
|
|
994
|
-
useEffect(() => {
|
|
995
|
-
if (!expanded) return;
|
|
996
|
-
if (!useChunkedBody) return;
|
|
997
|
-
if (responsePreview.status !== "idle") return;
|
|
998
|
-
if (activeTab !== "parsed" && activeTab !== "raw") return;
|
|
999
|
-
loadBodyChunk("response", 0);
|
|
1000
|
-
}, [activeTab, expanded, loadBodyChunk, responsePreview.status, useChunkedBody]);
|
|
1001
|
-
|
|
1002
|
-
useEffect(() => {
|
|
1003
|
-
const handleLogFocusRequest = (event: Event): void => {
|
|
1004
|
-
const request = readLogFocusRequest(event);
|
|
1005
|
-
if (request === null) return;
|
|
1006
|
-
if (request.logId !== log.id) return;
|
|
1007
|
-
setExpanded(true);
|
|
1008
|
-
userSelectedTabRef.current = true;
|
|
1009
|
-
setActiveTab(resolveFocusedTab(request.tab, anatomySegments));
|
|
1010
|
-
if (request.tab === "response" && request.toolCallIndex !== undefined) {
|
|
1011
|
-
setFocusedToolIndex(request.toolCallIndex);
|
|
1012
|
-
setToolFocusNonce((current) => current + 1);
|
|
1013
|
-
}
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
window.addEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
1017
|
-
return () => {
|
|
1018
|
-
window.removeEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
1019
|
-
};
|
|
1020
|
-
}, [anatomySegments, log.id]);
|
|
1021
|
-
|
|
1022
|
-
return (
|
|
1023
|
-
<TooltipProvider>
|
|
1024
|
-
<div
|
|
1025
|
-
className={cn(
|
|
1026
|
-
"bg-card/80 relative mb-1.5 overflow-hidden rounded-[8px] transition-[background-color,box-shadow]",
|
|
1027
|
-
expanded
|
|
1028
|
-
? "bg-card shadow-[0_18px_60px_rgba(0,0,0,0.30),0_0_0_1px_rgba(103,232,249,0.08)]"
|
|
1029
|
-
: "hover:bg-card",
|
|
1030
|
-
)}
|
|
1031
|
-
>
|
|
1032
|
-
<LogEntryHeader
|
|
1033
|
-
log={displayLog}
|
|
1034
|
-
displayNumber={displayNumber}
|
|
1035
|
-
messageCount={requestAnalysis.messageCount}
|
|
1036
|
-
toolCount={requestAnalysis.toolCount}
|
|
1037
|
-
toolEvents={toolEvents}
|
|
1038
|
-
expanded={expanded}
|
|
1039
|
-
onToggle={handleToggleExpanded}
|
|
1040
|
-
cacheTrend={cacheTrend}
|
|
1041
|
-
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
1042
|
-
showClientMetadata={showClientMetadata}
|
|
1043
|
-
timeDisplayFormat={timeDisplayFormat}
|
|
1044
|
-
activeTab={activeTab}
|
|
1045
|
-
tabActions={tabActions}
|
|
1046
|
-
onReplay={
|
|
1047
|
-
onCompareWithPrevious === undefined
|
|
1048
|
-
? undefined
|
|
1049
|
-
: () => {
|
|
1050
|
-
setReplayOpen(true);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
/>
|
|
1054
|
-
|
|
1055
|
-
{expanded && (
|
|
1056
|
-
<div
|
|
1057
|
-
className="relative bg-black/[0.14] pt-2"
|
|
1058
|
-
onClick={(e) => e.stopPropagation()}
|
|
1059
|
-
onKeyDown={(e) => e.stopPropagation()}
|
|
1060
|
-
>
|
|
1061
|
-
<div
|
|
1062
|
-
className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/[0.05] to-transparent"
|
|
1063
|
-
aria-hidden="true"
|
|
1064
|
-
/>
|
|
1065
|
-
<LazyFeatureBoundary feature="Log details">
|
|
1066
|
-
<Tabs value={activeTab} onValueChange={handleTabChange} className="gap-2">
|
|
1067
|
-
<TabsList
|
|
1068
|
-
variant="line"
|
|
1069
|
-
className="bg-white/[0.04] inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-[8px] p-1"
|
|
1070
|
-
>
|
|
1071
|
-
{shouldShowRawHeadersTab(
|
|
1072
|
-
viewMode,
|
|
1073
|
-
displayLog.rawHeaders !== undefined &&
|
|
1074
|
-
Object.keys(displayLog.rawHeaders).length > 0,
|
|
1075
|
-
) && (
|
|
1076
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
|
|
1077
|
-
Raw Headers
|
|
1078
|
-
</TabsTrigger>
|
|
1079
|
-
)}
|
|
1080
|
-
{shouldShowHeadersTab(
|
|
1081
|
-
viewMode,
|
|
1082
|
-
displayLog.headers !== undefined && Object.keys(displayLog.headers).length > 0,
|
|
1083
|
-
) && (
|
|
1084
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="headers">
|
|
1085
|
-
Headers
|
|
1086
|
-
</TabsTrigger>
|
|
1087
|
-
)}
|
|
1088
|
-
{anatomySegments !== null && (
|
|
1089
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="anatomy">
|
|
1090
|
-
Context
|
|
1091
|
-
</TabsTrigger>
|
|
1092
|
-
)}
|
|
1093
|
-
{shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
|
|
1094
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-request">
|
|
1095
|
-
Raw Request
|
|
1096
|
-
</TabsTrigger>
|
|
1097
|
-
)}
|
|
1098
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="request">
|
|
1099
|
-
Request
|
|
1100
|
-
</TabsTrigger>
|
|
1101
|
-
{viewMode === "full" && (
|
|
1102
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="raw">
|
|
1103
|
-
Raw Response
|
|
1104
|
-
</TabsTrigger>
|
|
1105
|
-
)}
|
|
1106
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="parsed">
|
|
1107
|
-
Response
|
|
1108
|
-
</TabsTrigger>
|
|
1109
|
-
{showSseTab && (
|
|
1110
|
-
<TabsTrigger className={TAB_TRIGGER_CLASS} value="sse">
|
|
1111
|
-
SSE
|
|
1112
|
-
</TabsTrigger>
|
|
1113
|
-
)}
|
|
1114
|
-
</TabsList>
|
|
1115
|
-
|
|
1116
|
-
{shouldShowRawRequestTab(resolvedFormat, viewMode, strip) && (
|
|
1117
|
-
<TabsContent value="raw-request">
|
|
1118
|
-
{activeTab === "raw-request" && (
|
|
1119
|
-
<ExpandedPanel>
|
|
1120
|
-
<FullBodyHydrationNotice
|
|
1121
|
-
status={bodyHydrationStatus}
|
|
1122
|
-
error={bodyHydrationError}
|
|
1123
|
-
footprint={fullBodyHydrationFootprint}
|
|
1124
|
-
onRetry={retryFullBodyHydration}
|
|
1125
|
-
/>
|
|
1126
|
-
{useChunkedBody && (
|
|
1127
|
-
<BodyPreviewNotice
|
|
1128
|
-
label="Request"
|
|
1129
|
-
state={requestPreview}
|
|
1130
|
-
expectedBytes={log.rawRequestBodyBytes ?? null}
|
|
1131
|
-
onLoadMore={() => loadBodyChunk("request", requestPreview.offset)}
|
|
1132
|
-
/>
|
|
1133
|
-
)}
|
|
1134
|
-
{rawRequestBodyForView === null ? (
|
|
1135
|
-
<EmptyState>
|
|
1136
|
-
{useChunkedBody ? "Request preview is loading" : "No request body"}
|
|
1137
|
-
</EmptyState>
|
|
1138
|
-
) : rawRequestExpansion.parsedData !== null ? (
|
|
1139
|
-
<Suspense fallback={<TabFallback />}>
|
|
1140
|
-
<LazyJsonViewer
|
|
1141
|
-
data={rawRequestExpansion.parsedData}
|
|
1142
|
-
bulkDepth={rawRequestExpansion.bulkDepth}
|
|
1143
|
-
bulkRevision={rawRequestExpansion.bulkRevision}
|
|
1144
|
-
/>
|
|
1145
|
-
</Suspense>
|
|
1146
|
-
) : (
|
|
1147
|
-
<RawTextBlock>{rawRequestBodyForView}</RawTextBlock>
|
|
1148
|
-
)}
|
|
1149
|
-
</ExpandedPanel>
|
|
1150
|
-
)}
|
|
1151
|
-
</TabsContent>
|
|
1152
|
-
)}
|
|
1153
|
-
|
|
1154
|
-
<TabsContent value="request">
|
|
1155
|
-
{activeTab === "request" && (
|
|
1156
|
-
<ExpandedPanel>
|
|
1157
|
-
<FullBodyHydrationNotice
|
|
1158
|
-
status={bodyHydrationStatus}
|
|
1159
|
-
error={bodyHydrationError}
|
|
1160
|
-
footprint={fullBodyHydrationFootprint}
|
|
1161
|
-
onRetry={retryFullBodyHydration}
|
|
1162
|
-
/>
|
|
1163
|
-
{useChunkedBody && (
|
|
1164
|
-
<BodyPreviewNotice
|
|
1165
|
-
label="Request"
|
|
1166
|
-
state={requestPreview}
|
|
1167
|
-
expectedBytes={log.rawRequestBodyBytes ?? null}
|
|
1168
|
-
onLoadMore={() => loadBodyChunk("request", requestPreview.offset)}
|
|
1169
|
-
/>
|
|
1170
|
-
)}
|
|
1171
|
-
{warnings.length > 0 && (
|
|
1172
|
-
<NoticeBlock tone="warning">
|
|
1173
|
-
<div className="flex items-center gap-2 font-semibold text-amber-300">
|
|
1174
|
-
<AlertTriangle className="size-3.5" />
|
|
1175
|
-
Tool schema warnings
|
|
1176
|
-
</div>
|
|
1177
|
-
<ul className="mt-2 space-y-1 text-muted-foreground">
|
|
1178
|
-
{warnings.map((warning) => (
|
|
1179
|
-
<li key={warning}>{warning}</li>
|
|
1180
|
-
))}
|
|
1181
|
-
</ul>
|
|
1182
|
-
</NoticeBlock>
|
|
1183
|
-
)}
|
|
1184
|
-
<RequestTools
|
|
1185
|
-
summary={requestTools}
|
|
1186
|
-
calledToolNames={calledToolNamesForView}
|
|
1187
|
-
/>
|
|
1188
|
-
{/* All tab actions (Copy, Expand, Diff with Raw, Diff with
|
|
1189
|
-
Previous) live in the log header. The body is just
|
|
1190
|
-
the diff or JSON view, depending on toggle state. */}
|
|
1191
|
-
{requestDiff ? (
|
|
1192
|
-
<RequestDiffContent
|
|
1193
|
-
rawBody={displayLog.rawRequestBody}
|
|
1194
|
-
displayedBody={displayedRequestBody}
|
|
1195
|
-
emptyLabel="No transformation applied; raw and sent request bodies are identical."
|
|
1196
|
-
/>
|
|
1197
|
-
) : (
|
|
1198
|
-
<div ref={requestJsonRef}>
|
|
1199
|
-
{displayedRequestBody === null ? (
|
|
1200
|
-
<EmptyState>
|
|
1201
|
-
{useChunkedBody ? "Request preview is loading" : "No request body"}
|
|
1202
|
-
</EmptyState>
|
|
1203
|
-
) : requestExpansion.parsedData !== null ? (
|
|
1204
|
-
<Suspense fallback={<TabFallback />}>
|
|
1205
|
-
<LazyJsonViewer
|
|
1206
|
-
data={requestExpansion.parsedData}
|
|
1207
|
-
bulkDepth={requestExpansion.bulkDepth}
|
|
1208
|
-
bulkRevision={requestExpansion.bulkRevision}
|
|
1209
|
-
anatomyPaths={anatomyPaths}
|
|
1210
|
-
expandToPath={expandToPath}
|
|
1211
|
-
/>
|
|
1212
|
-
</Suspense>
|
|
1213
|
-
) : (
|
|
1214
|
-
<RawTextBlock>{displayedRequestBody}</RawTextBlock>
|
|
1215
|
-
)}
|
|
1216
|
-
</div>
|
|
1217
|
-
)}
|
|
1218
|
-
</ExpandedPanel>
|
|
1219
|
-
)}
|
|
1220
|
-
</TabsContent>
|
|
1221
|
-
|
|
1222
|
-
{anatomySegments !== null && (
|
|
1223
|
-
<TabsContent value="anatomy">
|
|
1224
|
-
{activeTab === "anatomy" && (
|
|
1225
|
-
<ExpandedPanel className="p-0">
|
|
1226
|
-
<Suspense fallback={<TabFallback />}>
|
|
1227
|
-
<LazyRequestAnatomy
|
|
1228
|
-
parsed={requestExpansion.parsedData}
|
|
1229
|
-
inputTokens={displayLog.inputTokens ?? null}
|
|
1230
|
-
model={displayLog.model}
|
|
1231
|
-
segments={anatomySegments}
|
|
1232
|
-
onSegmentActivate={jumpToAnatomySegment}
|
|
1233
|
-
/>
|
|
1234
|
-
</Suspense>
|
|
1235
|
-
</ExpandedPanel>
|
|
1236
|
-
)}
|
|
1237
|
-
</TabsContent>
|
|
1238
|
-
)}
|
|
1239
|
-
|
|
1240
|
-
{shouldShowHeadersTab(
|
|
1241
|
-
viewMode,
|
|
1242
|
-
displayLog.headers !== undefined && Object.keys(displayLog.headers).length > 0,
|
|
1243
|
-
) && (
|
|
1244
|
-
<TabsContent value="headers">
|
|
1245
|
-
{activeTab === "headers" && (
|
|
1246
|
-
<ExpandedPanel>
|
|
1247
|
-
{/* Copy lives in the log header. */}
|
|
1248
|
-
<div className="flex justify-end gap-2 mb-2">
|
|
1249
|
-
{shouldShowHeadersDiffButton(
|
|
1250
|
-
viewMode,
|
|
1251
|
-
displayLog.rawHeaders !== undefined &&
|
|
1252
|
-
Object.keys(displayLog.rawHeaders).length > 0,
|
|
1253
|
-
) && (
|
|
1254
|
-
<DiffToggleButton
|
|
1255
|
-
active={headersDiff}
|
|
1256
|
-
onClick={(e) => {
|
|
1257
|
-
e.stopPropagation();
|
|
1258
|
-
setHeadersDiff(!headersDiff);
|
|
1259
|
-
}}
|
|
1260
|
-
/>
|
|
1261
|
-
)}
|
|
1262
|
-
</div>
|
|
1263
|
-
{headersDiff ? (
|
|
1264
|
-
<HeadersDiffContent
|
|
1265
|
-
rawHeaders={displayLog.rawHeaders}
|
|
1266
|
-
headers={displayLog.headers}
|
|
1267
|
-
emptyLabel="No transformation applied; raw and processed headers are identical."
|
|
1268
|
-
/>
|
|
1269
|
-
) : (
|
|
1270
|
-
<HeaderRows
|
|
1271
|
-
headers={displayLog.headers}
|
|
1272
|
-
emptyLabel="No headers captured"
|
|
1273
|
-
/>
|
|
1274
|
-
)}
|
|
1275
|
-
</ExpandedPanel>
|
|
1276
|
-
)}
|
|
1277
|
-
</TabsContent>
|
|
1278
|
-
)}
|
|
1279
|
-
|
|
1280
|
-
{shouldShowRawHeadersTab(
|
|
1281
|
-
viewMode,
|
|
1282
|
-
displayLog.rawHeaders !== undefined &&
|
|
1283
|
-
Object.keys(displayLog.rawHeaders).length > 0,
|
|
1284
|
-
) && (
|
|
1285
|
-
<TabsContent value="raw-headers">
|
|
1286
|
-
{activeTab === "raw-headers" && (
|
|
1287
|
-
<ExpandedPanel>
|
|
1288
|
-
{/* Copy lives in the log header. */}
|
|
1289
|
-
<HeaderRows
|
|
1290
|
-
headers={displayLog.rawHeaders}
|
|
1291
|
-
emptyLabel="No raw headers captured"
|
|
1292
|
-
/>
|
|
1293
|
-
</ExpandedPanel>
|
|
1294
|
-
)}
|
|
1295
|
-
</TabsContent>
|
|
1296
|
-
)}
|
|
1297
|
-
|
|
1298
|
-
<TabsContent value="raw">
|
|
1299
|
-
{activeTab === "raw" && (
|
|
1300
|
-
<ExpandedPanel className="space-y-3">
|
|
1301
|
-
<FullBodyHydrationNotice
|
|
1302
|
-
status={bodyHydrationStatus}
|
|
1303
|
-
error={bodyHydrationError}
|
|
1304
|
-
footprint={fullBodyHydrationFootprint}
|
|
1305
|
-
onRetry={retryFullBodyHydration}
|
|
1306
|
-
/>
|
|
1307
|
-
{useChunkedBody && (
|
|
1308
|
-
<BodyPreviewNotice
|
|
1309
|
-
label="Response"
|
|
1310
|
-
state={responsePreview}
|
|
1311
|
-
expectedBytes={log.responseTextBytes ?? null}
|
|
1312
|
-
onLoadMore={() => loadBodyChunk("response", responsePreview.offset)}
|
|
1313
|
-
/>
|
|
1314
|
-
)}
|
|
1315
|
-
<SseInlineNotice
|
|
1316
|
-
error={displayLog.error}
|
|
1317
|
-
onOpenSse={showSseTab ? openSseTab : undefined}
|
|
1318
|
-
/>
|
|
1319
|
-
{responseTextForView !== null && responsePreviewShouldParse ? (
|
|
1320
|
-
<Suspense fallback={<TabFallback />}>
|
|
1321
|
-
<LazyJsonViewerFromString
|
|
1322
|
-
text={responseTextForView}
|
|
1323
|
-
defaultExpandDepth={0}
|
|
1324
|
-
bulkDepth={responseExpansion.bulkDepth}
|
|
1325
|
-
bulkRevision={responseExpansion.bulkRevision}
|
|
1326
|
-
/>
|
|
1327
|
-
</Suspense>
|
|
1328
|
-
) : responseTextForView !== null ? (
|
|
1329
|
-
<RawTextBlock>{responseTextForView}</RawTextBlock>
|
|
1330
|
-
) : (
|
|
1331
|
-
<EmptyState>
|
|
1332
|
-
{useChunkedBody ? "Response preview is loading" : "No response"}
|
|
1333
|
-
</EmptyState>
|
|
1334
|
-
)}
|
|
1335
|
-
{showSseTab && <SseChunksNotice onOpenSse={openSseTab} />}
|
|
1336
|
-
</ExpandedPanel>
|
|
1337
|
-
)}
|
|
1338
|
-
</TabsContent>
|
|
1339
|
-
|
|
1340
|
-
{showSseTab && (
|
|
1341
|
-
<TabsContent value="sse">
|
|
1342
|
-
{activeTab === "sse" && (
|
|
1343
|
-
<ExpandedPanel className="space-y-3">
|
|
1344
|
-
{displayLog.error !== undefined && displayLog.error !== null && (
|
|
1345
|
-
<NoticeBlock tone="danger" title="SSE Error">
|
|
1346
|
-
<span className="font-mono">{displayLog.error}</span>
|
|
1347
|
-
</NoticeBlock>
|
|
1348
|
-
)}
|
|
1349
|
-
<Suspense fallback={<TabFallback />}>
|
|
1350
|
-
<LazyStreamingChunkSequence
|
|
1351
|
-
logId={displayLog.id}
|
|
1352
|
-
truncated={displayLog.streamingChunksPath !== null}
|
|
1353
|
-
/>
|
|
1354
|
-
</Suspense>
|
|
1355
|
-
</ExpandedPanel>
|
|
1356
|
-
)}
|
|
1357
|
-
</TabsContent>
|
|
1358
|
-
)}
|
|
1359
|
-
|
|
1360
|
-
<TabsContent value="parsed">
|
|
1361
|
-
{activeTab === "parsed" && (
|
|
1362
|
-
<ExpandedPanel>
|
|
1363
|
-
<FullBodyHydrationNotice
|
|
1364
|
-
status={bodyHydrationStatus}
|
|
1365
|
-
error={bodyHydrationError}
|
|
1366
|
-
footprint={fullBodyHydrationFootprint}
|
|
1367
|
-
onRetry={retryFullBodyHydration}
|
|
1368
|
-
/>
|
|
1369
|
-
{useChunkedBody && (
|
|
1370
|
-
<BodyPreviewNotice
|
|
1371
|
-
label="Response"
|
|
1372
|
-
state={responsePreview}
|
|
1373
|
-
expectedBytes={log.responseTextBytes ?? null}
|
|
1374
|
-
onLoadMore={() => loadBodyChunk("response", responsePreview.offset)}
|
|
1375
|
-
/>
|
|
1376
|
-
)}
|
|
1377
|
-
<SseInlineNotice
|
|
1378
|
-
error={displayLog.error}
|
|
1379
|
-
onOpenSse={showSseTab ? openSseTab : undefined}
|
|
1380
|
-
/>
|
|
1381
|
-
{responsePreviewIsActive && !responsePreviewShouldParse ? (
|
|
1382
|
-
<RawTextBlock>{responseTextForView}</RawTextBlock>
|
|
1383
|
-
) : (
|
|
1384
|
-
<Suspense fallback={<TabFallback />}>
|
|
1385
|
-
<LazyResponseView
|
|
1386
|
-
responseText={responseTextForView}
|
|
1387
|
-
responseStatus={displayLog.responseStatus}
|
|
1388
|
-
streaming={displayLog.streaming}
|
|
1389
|
-
inputTokens={displayLog.inputTokens}
|
|
1390
|
-
outputTokens={displayLog.outputTokens}
|
|
1391
|
-
cacheCreationInputTokens={displayLog.cacheCreationInputTokens}
|
|
1392
|
-
cacheReadInputTokens={displayLog.cacheReadInputTokens}
|
|
1393
|
-
apiFormat={resolvedFormat}
|
|
1394
|
-
focusedToolIndex={focusedToolIndex}
|
|
1395
|
-
toolFocusNonce={toolFocusNonce}
|
|
1396
|
-
/>
|
|
1397
|
-
</Suspense>
|
|
1398
|
-
)}
|
|
1399
|
-
</ExpandedPanel>
|
|
1400
|
-
)}
|
|
1401
|
-
</TabsContent>
|
|
1402
|
-
</Tabs>
|
|
1403
|
-
</LazyFeatureBoundary>
|
|
1404
|
-
</div>
|
|
1405
|
-
)}
|
|
1406
|
-
</div>
|
|
1407
|
-
<LazyFeatureBoundary feature="Replay">
|
|
1408
|
-
<Suspense fallback={null}>
|
|
1409
|
-
<LazyReplayDialog
|
|
1410
|
-
log={displayLog}
|
|
1411
|
-
displayNumber={displayNumber}
|
|
1412
|
-
open={replayOpen}
|
|
1413
|
-
onOpenChange={setReplayOpen}
|
|
1414
|
-
/>
|
|
1415
|
-
</Suspense>
|
|
1416
|
-
</LazyFeatureBoundary>
|
|
1417
|
-
</TooltipProvider>
|
|
1418
|
-
);
|
|
1419
|
-
});
|