@tonyclaw/agent-inspector 4.0.1 → 4.0.3
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 +193 -110
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-CTHqmroT.mjs → router-ChILzhmz.mjs} +92 -4
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-Co3cUF_I.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-jFcrL37X.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-BPzt_VpM.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-C6O_zuwI.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-Ca6NvB7L.js} +1 -1
- package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BzqKcveJ.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-9pCZIsS_.js} +1 -1
- package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-BMP3KVGR.js} +1 -1
- package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-y4eRoT1H.js} +1 -1
- package/.output/ui/assets/{index-Ea-c8hPg.js → index-CH5w-haG.js} +1 -1
- package/.output/ui/assets/{index-zVVS53Hd.js → index-CmocEhyU.js} +1 -1
- package/.output/ui/assets/{index-Du4qNOOt.js → index-DT8sMJ_n.js} +2 -2
- package/.output/ui/assets/{index-gvFj_9K6.js → index-bfsbIRZa.js} +1 -1
- package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-BfFOSDG5.js} +1 -1
- package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkbVkOsp.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/package.json +5 -10
- 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 -1433
- 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 -258
- 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,777 +0,0 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
|
-
import { existsSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import { Worker } from "node:worker_threads";
|
|
6
|
-
import { writeChunks } from "./chunkStorage";
|
|
7
|
-
import { formatForPath } from "./formats";
|
|
8
|
-
import { appendLogEntry, logger } from "./logger";
|
|
9
|
-
import type { RawStreamSource } from "./rawStreamCapture";
|
|
10
|
-
import type { CapturedLog } from "./schemas";
|
|
11
|
-
import {
|
|
12
|
-
getSessionProcess,
|
|
13
|
-
getSessionProcessPendingCount,
|
|
14
|
-
isSessionProcessAvailable,
|
|
15
|
-
} from "./sessionProcess";
|
|
16
|
-
import { persistFinalizedLogUpdate } from "./store";
|
|
17
|
-
import { computeTokensPerSecond } from "./streamTiming";
|
|
18
|
-
import { analyzeToolSchemaWarnings } from "./toolSchemaWarnings";
|
|
19
|
-
import { isPlainRecord, safeGetOwnProperty } from "../lib/objectUtils";
|
|
20
|
-
|
|
21
|
-
type BaseFinalizeLogJob = {
|
|
22
|
-
log: CapturedLog;
|
|
23
|
-
upstreamUrl: string;
|
|
24
|
-
elapsedMs: number;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export type FinalizeNonStreamingLogJob = BaseFinalizeLogJob & {
|
|
28
|
-
type: "non-streaming";
|
|
29
|
-
responseStatus: number;
|
|
30
|
-
responseBody: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type FinalizeStreamingLogJob = BaseFinalizeLogJob & {
|
|
34
|
-
type: "streaming";
|
|
35
|
-
responseStatus: number;
|
|
36
|
-
rawStream: string | RawStreamSource;
|
|
37
|
-
collectStreamingChunks: boolean;
|
|
38
|
-
firstChunkMs: number | null;
|
|
39
|
-
totalStreamMs: number;
|
|
40
|
-
terminalError?: string | null;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type FinalizeStreamAbortLogJob = BaseFinalizeLogJob & {
|
|
44
|
-
type: "stream-abort";
|
|
45
|
-
rawStream: string | RawStreamSource;
|
|
46
|
-
hasChunks: boolean;
|
|
47
|
-
collectStreamingChunks: boolean;
|
|
48
|
-
firstChunkMs: number | null;
|
|
49
|
-
totalStreamMs: number;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export type FinalizeLogJob =
|
|
53
|
-
| FinalizeNonStreamingLogJob
|
|
54
|
-
| FinalizeStreamingLogJob
|
|
55
|
-
| FinalizeStreamAbortLogJob;
|
|
56
|
-
|
|
57
|
-
export type FinalizeLogResult = {
|
|
58
|
-
log: CapturedLog;
|
|
59
|
-
upstreamUrl: string;
|
|
60
|
-
error: string | null;
|
|
61
|
-
cleanupRawStreamPath?: string | null;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const DEFAULT_MAX_FINALIZE_STREAM_BYTES = 20 * 1024 * 1024;
|
|
65
|
-
const DEFAULT_FINALIZE_JOB_TIMEOUT_MS = 15_000;
|
|
66
|
-
|
|
67
|
-
function resolveMaxFinalizeStreamBytes(): number {
|
|
68
|
-
const raw = process.env["AGENT_INSPECTOR_MAX_FINALIZE_STREAM_BYTES"];
|
|
69
|
-
if (raw === undefined || raw === "") return DEFAULT_MAX_FINALIZE_STREAM_BYTES;
|
|
70
|
-
const parsed = Number(raw);
|
|
71
|
-
if (!Number.isFinite(parsed) || parsed < 1) return DEFAULT_MAX_FINALIZE_STREAM_BYTES;
|
|
72
|
-
return Math.floor(parsed);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function resolveFinalizeJobTimeoutMs(): number {
|
|
76
|
-
const raw = process.env["AGENT_INSPECTOR_FINALIZE_JOB_TIMEOUT_MS"];
|
|
77
|
-
if (raw === undefined || raw === "") return DEFAULT_FINALIZE_JOB_TIMEOUT_MS;
|
|
78
|
-
const parsed = Number(raw);
|
|
79
|
-
if (!Number.isFinite(parsed) || parsed < 100) return DEFAULT_FINALIZE_JOB_TIMEOUT_MS;
|
|
80
|
-
return Math.floor(parsed);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function buildFileLogEntry(log: CapturedLog, upstreamUrl: string): Record<string, unknown> {
|
|
84
|
-
return {
|
|
85
|
-
timestamp: log.timestamp,
|
|
86
|
-
id: log.id,
|
|
87
|
-
method: log.method,
|
|
88
|
-
path: log.path,
|
|
89
|
-
model: log.model,
|
|
90
|
-
sessionId: log.sessionId,
|
|
91
|
-
rawRequestBody: log.rawRequestBody,
|
|
92
|
-
responseStatus: log.responseStatus,
|
|
93
|
-
responseText: log.responseText,
|
|
94
|
-
inputTokens: log.inputTokens,
|
|
95
|
-
outputTokens: log.outputTokens,
|
|
96
|
-
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
97
|
-
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
98
|
-
elapsedMs: log.elapsedMs,
|
|
99
|
-
firstChunkMs: log.firstChunkMs,
|
|
100
|
-
totalStreamMs: log.totalStreamMs,
|
|
101
|
-
tokensPerSecond: log.tokensPerSecond,
|
|
102
|
-
streaming: log.streaming,
|
|
103
|
-
userAgent: log.userAgent,
|
|
104
|
-
origin: log.origin,
|
|
105
|
-
upstreamUrl,
|
|
106
|
-
clientPort: log.clientPort,
|
|
107
|
-
clientPid: log.clientPid,
|
|
108
|
-
clientCwd: log.clientCwd,
|
|
109
|
-
clientProjectFolder: log.clientProjectFolder,
|
|
110
|
-
streamingChunks: log.streamingChunks,
|
|
111
|
-
streamingChunksPath: log.streamingChunksPath,
|
|
112
|
-
captureIncomplete: log.captureIncomplete ?? false,
|
|
113
|
-
droppedChunks: log.droppedChunks ?? 0,
|
|
114
|
-
droppedBytes: log.droppedBytes ?? 0,
|
|
115
|
-
captureIncompleteReason: log.captureIncompleteReason ?? null,
|
|
116
|
-
warnings: log.warnings,
|
|
117
|
-
error: log.error,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function cloneLog(log: CapturedLog): CapturedLog {
|
|
122
|
-
return { ...log };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function errorMessage(err: unknown): string {
|
|
126
|
-
return err instanceof Error ? err.message : String(err);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function persistStreamingChunks(log: CapturedLog): void {
|
|
130
|
-
if (log.streamingChunks !== undefined && log.streamingChunks.chunks.length > 0) {
|
|
131
|
-
const chunkPath = writeChunks(
|
|
132
|
-
log.id,
|
|
133
|
-
log.streamingChunks.chunks,
|
|
134
|
-
log.streamingChunks.truncated,
|
|
135
|
-
);
|
|
136
|
-
log.streamingChunksPath = chunkPath;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function finalizeWithError(
|
|
141
|
-
job: FinalizeLogJob,
|
|
142
|
-
log: CapturedLog,
|
|
143
|
-
fallbackStatus: number,
|
|
144
|
-
fallbackResponseText: string,
|
|
145
|
-
err: unknown,
|
|
146
|
-
cleanupRawStreamPath: string | null = null,
|
|
147
|
-
): FinalizeLogResult {
|
|
148
|
-
const message = errorMessage(err);
|
|
149
|
-
logger.error(`[logFinalizer] Failed to finalize log #${log.id}:`, message);
|
|
150
|
-
log.responseStatus = log.responseStatus ?? fallbackStatus;
|
|
151
|
-
log.responseText = log.responseText ?? fallbackResponseText;
|
|
152
|
-
log.elapsedMs = job.elapsedMs;
|
|
153
|
-
applyOptionalStreamingTiming(log, job);
|
|
154
|
-
log.error = message;
|
|
155
|
-
return { log, upstreamUrl: job.upstreamUrl, error: message, cleanupRawStreamPath };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function applyOptionalStreamingTiming(log: CapturedLog, job: FinalizeLogJob): void {
|
|
159
|
-
switch (job.type) {
|
|
160
|
-
case "non-streaming":
|
|
161
|
-
log.firstChunkMs = null;
|
|
162
|
-
log.totalStreamMs = null;
|
|
163
|
-
log.tokensPerSecond = null;
|
|
164
|
-
return;
|
|
165
|
-
case "streaming":
|
|
166
|
-
case "stream-abort":
|
|
167
|
-
log.firstChunkMs = job.firstChunkMs;
|
|
168
|
-
log.totalStreamMs = job.totalStreamMs;
|
|
169
|
-
log.tokensPerSecond = computeTokensPerSecond({
|
|
170
|
-
outputTokens: log.outputTokens,
|
|
171
|
-
firstChunkMs: job.firstChunkMs,
|
|
172
|
-
totalStreamMs: job.totalStreamMs,
|
|
173
|
-
});
|
|
174
|
-
return;
|
|
175
|
-
default: {
|
|
176
|
-
const _exhaustive: never = job;
|
|
177
|
-
logger.error(`[logFinalizer] Unhandled job type: ${JSON.stringify(_exhaustive)}`);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
type ToolSchemaWarningAnalysisJob = {
|
|
184
|
-
log: CapturedLog;
|
|
185
|
-
upstreamUrl: string;
|
|
186
|
-
error: string | null;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
let toolSchemaWarningQueue: Promise<void> = Promise.resolve();
|
|
190
|
-
|
|
191
|
-
function deferToolSchemaWarningAnalysis(): Promise<void> {
|
|
192
|
-
return new Promise((resolve) => {
|
|
193
|
-
setTimeout(resolve, 0);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function withToolSchemaWarnings(log: CapturedLog): CapturedLog | null {
|
|
198
|
-
const warnings = analyzeToolSchemaWarnings({
|
|
199
|
-
rawRequestBody: log.rawRequestBody,
|
|
200
|
-
responseText: log.responseText,
|
|
201
|
-
apiFormat: log.apiFormat,
|
|
202
|
-
});
|
|
203
|
-
if (warnings.length === 0) return null;
|
|
204
|
-
const mergedWarnings = Array.from(new Set([...(log.warnings ?? []), ...warnings]));
|
|
205
|
-
if (mergedWarnings.length === (log.warnings?.length ?? 0)) return null;
|
|
206
|
-
return { ...log, warnings: mergedWarnings };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function runToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob): Promise<void> {
|
|
210
|
-
await deferToolSchemaWarningAnalysis();
|
|
211
|
-
const updatedLog = withToolSchemaWarnings(job.log);
|
|
212
|
-
if (updatedLog === null) return;
|
|
213
|
-
|
|
214
|
-
appendLogEntry({ ...buildFileLogEntry(updatedLog, job.upstreamUrl), error: job.error });
|
|
215
|
-
await persistFinalizedLogUpdate(updatedLog);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function scheduleToolSchemaWarningAnalysis(job: ToolSchemaWarningAnalysisJob): void {
|
|
219
|
-
toolSchemaWarningQueue = toolSchemaWarningQueue
|
|
220
|
-
.catch(() => undefined)
|
|
221
|
-
.then(() => runToolSchemaWarningAnalysis(job))
|
|
222
|
-
.catch((err: unknown) => {
|
|
223
|
-
logger.warn(
|
|
224
|
-
`[logFinalizer] Tool schema warning analysis failed for log #${job.log.id}:`,
|
|
225
|
-
errorMessage(err),
|
|
226
|
-
);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export async function flushToolSchemaWarningAnalysis(): Promise<void> {
|
|
231
|
-
await toolSchemaWarningQueue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function finalizeNonStreaming(
|
|
235
|
-
job: FinalizeNonStreamingLogJob,
|
|
236
|
-
log: CapturedLog,
|
|
237
|
-
): FinalizeLogResult {
|
|
238
|
-
const formatHandler = formatForPath(log.path);
|
|
239
|
-
if (formatHandler === null) {
|
|
240
|
-
return finalizeWithError(job, log, job.responseStatus, job.responseBody, "Unsupported format");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
const tokens = formatHandler.extractTokens(job.responseBody);
|
|
245
|
-
|
|
246
|
-
log.elapsedMs = job.elapsedMs;
|
|
247
|
-
log.firstChunkMs = null;
|
|
248
|
-
log.totalStreamMs = null;
|
|
249
|
-
log.tokensPerSecond = null;
|
|
250
|
-
log.responseStatus = job.responseStatus;
|
|
251
|
-
log.responseText = job.responseBody;
|
|
252
|
-
log.inputTokens = tokens.inputTokens;
|
|
253
|
-
log.outputTokens = tokens.outputTokens;
|
|
254
|
-
log.cacheCreationInputTokens = tokens.cacheCreationInputTokens;
|
|
255
|
-
log.cacheReadInputTokens = tokens.cacheReadInputTokens;
|
|
256
|
-
|
|
257
|
-
return { log, upstreamUrl: job.upstreamUrl, error: null };
|
|
258
|
-
} catch (err) {
|
|
259
|
-
return finalizeWithError(job, log, job.responseStatus, job.responseBody, err);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function cleanupPathForRawStream(rawStream: string | RawStreamSource): string | null {
|
|
264
|
-
if (typeof rawStream === "string") return null;
|
|
265
|
-
switch (rawStream.type) {
|
|
266
|
-
case "memory":
|
|
267
|
-
return null;
|
|
268
|
-
case "file":
|
|
269
|
-
return rawStream.path;
|
|
270
|
-
default: {
|
|
271
|
-
const _exhaustive: never = rawStream;
|
|
272
|
-
logger.error(`[logFinalizer] Unhandled raw stream type: ${JSON.stringify(_exhaustive)}`);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
type OversizedRawStream = {
|
|
279
|
-
byteLength: number;
|
|
280
|
-
limit: number;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
function rawStreamByteLength(rawStream: string | RawStreamSource): number | null {
|
|
284
|
-
if (typeof rawStream === "string") {
|
|
285
|
-
return Buffer.byteLength(rawStream, "utf8");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
switch (rawStream.type) {
|
|
289
|
-
case "memory":
|
|
290
|
-
return Buffer.byteLength(rawStream.rawStream, "utf8");
|
|
291
|
-
case "file":
|
|
292
|
-
try {
|
|
293
|
-
return statSync(rawStream.path).size;
|
|
294
|
-
} catch {
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
default: {
|
|
298
|
-
const _exhaustive: never = rawStream;
|
|
299
|
-
logger.error(`[logFinalizer] Unhandled raw stream type: ${JSON.stringify(_exhaustive)}`);
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function getOversizedRawStream(rawStream: string | RawStreamSource): OversizedRawStream | null {
|
|
306
|
-
const limit = resolveMaxFinalizeStreamBytes();
|
|
307
|
-
const byteLength = rawStreamByteLength(rawStream);
|
|
308
|
-
if (byteLength === null || byteLength <= limit) return null;
|
|
309
|
-
return { byteLength, limit };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function formatBytes(bytes: number): string {
|
|
313
|
-
if (bytes < 1024) return `${bytes} bytes`;
|
|
314
|
-
if (bytes < 1024 * 1024) return `${Math.ceil(bytes / 1024)} KB`;
|
|
315
|
-
return `${Math.ceil(bytes / (1024 * 1024))} MB`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function oversizedRawStreamText(oversized: OversizedRawStream): string {
|
|
319
|
-
return (
|
|
320
|
-
`Streaming response omitted because the captured raw stream is ${formatBytes(
|
|
321
|
-
oversized.byteLength,
|
|
322
|
-
)}, exceeding the finalize limit of ${formatBytes(oversized.limit)}. ` +
|
|
323
|
-
"Increase AGENT_INSPECTOR_MAX_FINALIZE_STREAM_BYTES if this response must be parsed."
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function readRawStream(rawStream: string | RawStreamSource): {
|
|
328
|
-
rawStreamText: string;
|
|
329
|
-
cleanupRawStreamPath: string | null;
|
|
330
|
-
} {
|
|
331
|
-
if (typeof rawStream === "string") {
|
|
332
|
-
return { rawStreamText: rawStream, cleanupRawStreamPath: null };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
switch (rawStream.type) {
|
|
336
|
-
case "memory":
|
|
337
|
-
return { rawStreamText: rawStream.rawStream, cleanupRawStreamPath: null };
|
|
338
|
-
case "file":
|
|
339
|
-
return {
|
|
340
|
-
rawStreamText: readFileSync(rawStream.path, "utf-8"),
|
|
341
|
-
cleanupRawStreamPath: rawStream.path,
|
|
342
|
-
};
|
|
343
|
-
default: {
|
|
344
|
-
const _exhaustive: never = rawStream;
|
|
345
|
-
logger.error(`[logFinalizer] Unhandled raw stream type: ${JSON.stringify(_exhaustive)}`);
|
|
346
|
-
return { rawStreamText: "", cleanupRawStreamPath: null };
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function finalizeStreaming(job: FinalizeStreamingLogJob, log: CapturedLog): FinalizeLogResult {
|
|
352
|
-
const formatHandler = formatForPath(log.path);
|
|
353
|
-
if (formatHandler === null) {
|
|
354
|
-
return finalizeWithError(
|
|
355
|
-
job,
|
|
356
|
-
log,
|
|
357
|
-
job.responseStatus,
|
|
358
|
-
"Streaming response unavailable",
|
|
359
|
-
"Unsupported format",
|
|
360
|
-
cleanupPathForRawStream(job.rawStream),
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const oversized = getOversizedRawStream(job.rawStream);
|
|
365
|
-
if (oversized !== null) {
|
|
366
|
-
log.elapsedMs = job.elapsedMs;
|
|
367
|
-
applyOptionalStreamingTiming(log, job);
|
|
368
|
-
log.responseStatus = job.responseStatus;
|
|
369
|
-
log.responseText = oversizedRawStreamText(oversized);
|
|
370
|
-
return {
|
|
371
|
-
log,
|
|
372
|
-
upstreamUrl: job.upstreamUrl,
|
|
373
|
-
error: null,
|
|
374
|
-
cleanupRawStreamPath: cleanupPathForRawStream(job.rawStream),
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
let rawStreamText = "";
|
|
379
|
-
let cleanupRawStreamPath: string | null = null;
|
|
380
|
-
try {
|
|
381
|
-
const raw = readRawStream(job.rawStream);
|
|
382
|
-
rawStreamText = raw.rawStreamText;
|
|
383
|
-
cleanupRawStreamPath = raw.cleanupRawStreamPath;
|
|
384
|
-
log.elapsedMs = job.elapsedMs;
|
|
385
|
-
log.responseStatus = job.responseStatus;
|
|
386
|
-
log.responseText = formatHandler.extractStream(
|
|
387
|
-
rawStreamText,
|
|
388
|
-
log,
|
|
389
|
-
log.model ?? undefined,
|
|
390
|
-
job.collectStreamingChunks,
|
|
391
|
-
);
|
|
392
|
-
applyOptionalStreamingTiming(log, job);
|
|
393
|
-
persistStreamingChunks(log);
|
|
394
|
-
|
|
395
|
-
return { log, upstreamUrl: job.upstreamUrl, error: null, cleanupRawStreamPath };
|
|
396
|
-
} catch (err) {
|
|
397
|
-
return finalizeWithError(
|
|
398
|
-
job,
|
|
399
|
-
log,
|
|
400
|
-
job.responseStatus,
|
|
401
|
-
rawStreamText === "" ? "Streaming response unavailable" : rawStreamText,
|
|
402
|
-
err,
|
|
403
|
-
cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream),
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function finalizeStreamAbort(job: FinalizeStreamAbortLogJob, log: CapturedLog): FinalizeLogResult {
|
|
409
|
-
const formatHandler = formatForPath(log.path);
|
|
410
|
-
if (formatHandler === null && job.hasChunks) {
|
|
411
|
-
return finalizeWithError(
|
|
412
|
-
job,
|
|
413
|
-
log,
|
|
414
|
-
499,
|
|
415
|
-
"Client aborted",
|
|
416
|
-
"Unsupported format",
|
|
417
|
-
cleanupPathForRawStream(job.rawStream),
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
let rawStreamText = "";
|
|
422
|
-
let cleanupRawStreamPath: string | null = null;
|
|
423
|
-
try {
|
|
424
|
-
log.elapsedMs = job.elapsedMs;
|
|
425
|
-
applyOptionalStreamingTiming(log, job);
|
|
426
|
-
if (job.hasChunks && formatHandler !== null) {
|
|
427
|
-
const oversized = getOversizedRawStream(job.rawStream);
|
|
428
|
-
if (oversized !== null) {
|
|
429
|
-
log.responseText = `Client aborted. ${oversizedRawStreamText(oversized)}`;
|
|
430
|
-
return {
|
|
431
|
-
log,
|
|
432
|
-
upstreamUrl: job.upstreamUrl,
|
|
433
|
-
error: "Client aborted",
|
|
434
|
-
cleanupRawStreamPath: cleanupPathForRawStream(job.rawStream),
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
const raw = readRawStream(job.rawStream);
|
|
438
|
-
rawStreamText = raw.rawStreamText;
|
|
439
|
-
cleanupRawStreamPath = raw.cleanupRawStreamPath;
|
|
440
|
-
log.responseText = formatHandler.extractStream(
|
|
441
|
-
rawStreamText,
|
|
442
|
-
log,
|
|
443
|
-
log.model ?? undefined,
|
|
444
|
-
job.collectStreamingChunks,
|
|
445
|
-
);
|
|
446
|
-
applyOptionalStreamingTiming(log, job);
|
|
447
|
-
persistStreamingChunks(log);
|
|
448
|
-
} else {
|
|
449
|
-
log.responseText = "Client aborted";
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted", cleanupRawStreamPath };
|
|
453
|
-
} catch (err) {
|
|
454
|
-
return finalizeWithError(
|
|
455
|
-
job,
|
|
456
|
-
log,
|
|
457
|
-
499,
|
|
458
|
-
rawStreamText === "" ? "Client aborted" : rawStreamText,
|
|
459
|
-
err,
|
|
460
|
-
cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream),
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
export function buildFinalizeLogResult(job: FinalizeLogJob): FinalizeLogResult {
|
|
466
|
-
const log = cloneLog(job.log);
|
|
467
|
-
const upstreamUrl = job.upstreamUrl;
|
|
468
|
-
|
|
469
|
-
switch (job.type) {
|
|
470
|
-
case "non-streaming":
|
|
471
|
-
return finalizeNonStreaming(job, log);
|
|
472
|
-
case "streaming": {
|
|
473
|
-
const result = finalizeStreaming(job, log);
|
|
474
|
-
if (job.terminalError !== undefined && job.terminalError !== null) {
|
|
475
|
-
result.log.error = job.terminalError;
|
|
476
|
-
}
|
|
477
|
-
return result;
|
|
478
|
-
}
|
|
479
|
-
case "stream-abort":
|
|
480
|
-
return finalizeStreamAbort(job, log);
|
|
481
|
-
default: {
|
|
482
|
-
const _exhaustive: never = job;
|
|
483
|
-
logger.error(`[logFinalizer] Unhandled job type: ${JSON.stringify(_exhaustive)}`);
|
|
484
|
-
return {
|
|
485
|
-
log,
|
|
486
|
-
upstreamUrl,
|
|
487
|
-
error: "Unhandled job type",
|
|
488
|
-
cleanupRawStreamPath: null,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
export function buildFinalizeTimeoutResult(
|
|
495
|
-
job: FinalizeLogJob,
|
|
496
|
-
timeoutMs: number,
|
|
497
|
-
): FinalizeLogResult {
|
|
498
|
-
const log = cloneLog(job.log);
|
|
499
|
-
const message = `Log finalization timed out after ${String(timeoutMs)}ms`;
|
|
500
|
-
log.elapsedMs = job.elapsedMs;
|
|
501
|
-
applyOptionalStreamingTiming(log, job);
|
|
502
|
-
let fallbackStatus = 499;
|
|
503
|
-
let cleanupRawStreamPath: string | null = null;
|
|
504
|
-
switch (job.type) {
|
|
505
|
-
case "non-streaming":
|
|
506
|
-
case "streaming":
|
|
507
|
-
fallbackStatus = job.responseStatus;
|
|
508
|
-
cleanupRawStreamPath =
|
|
509
|
-
job.type === "streaming" ? cleanupPathForRawStream(job.rawStream) : null;
|
|
510
|
-
break;
|
|
511
|
-
case "stream-abort":
|
|
512
|
-
cleanupRawStreamPath = cleanupPathForRawStream(job.rawStream);
|
|
513
|
-
break;
|
|
514
|
-
default: {
|
|
515
|
-
const _exhaustive: never = job;
|
|
516
|
-
logger.error(`[logFinalizer] Unhandled timeout job type: ${JSON.stringify(_exhaustive)}`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
log.responseStatus = log.responseStatus ?? fallbackStatus;
|
|
520
|
-
log.responseText = log.responseText ?? message;
|
|
521
|
-
log.captureIncomplete = true;
|
|
522
|
-
log.captureIncompleteReason = "finalize-timeout";
|
|
523
|
-
log.error = message;
|
|
524
|
-
return {
|
|
525
|
-
log,
|
|
526
|
-
upstreamUrl: job.upstreamUrl,
|
|
527
|
-
error: message,
|
|
528
|
-
cleanupRawStreamPath,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
export async function commitFinalizeLogResult(result: FinalizeLogResult): Promise<void> {
|
|
533
|
-
const persistedError =
|
|
534
|
-
result.log.captureIncompleteReason === "upstream-stream-error"
|
|
535
|
-
? (result.log.error ?? result.error ?? null)
|
|
536
|
-
: (result.error ?? result.log.error ?? null);
|
|
537
|
-
result.log.error = persistedError;
|
|
538
|
-
appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: persistedError });
|
|
539
|
-
await persistFinalizedLogUpdate(result.log);
|
|
540
|
-
scheduleToolSchemaWarningAnalysis({
|
|
541
|
-
log: cloneLog(result.log),
|
|
542
|
-
upstreamUrl: result.upstreamUrl,
|
|
543
|
-
error: persistedError,
|
|
544
|
-
});
|
|
545
|
-
if (result.cleanupRawStreamPath !== undefined && result.cleanupRawStreamPath !== null) {
|
|
546
|
-
try {
|
|
547
|
-
rmSync(result.cleanupRawStreamPath, { force: true });
|
|
548
|
-
} catch (err) {
|
|
549
|
-
logger.warn(
|
|
550
|
-
"[logFinalizer] Failed to remove raw stream temp file:",
|
|
551
|
-
result.cleanupRawStreamPath,
|
|
552
|
-
errorMessage(err),
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// ── Worker pool ────────────────────────────────────────────────
|
|
559
|
-
// buildFinalizeLogResult is the CPU-heavy step (token extraction,
|
|
560
|
-
// SSE parsing, chunk persistence). We offload it to a fixed pool
|
|
561
|
-
// of Worker Threads so the main thread stays responsive even under
|
|
562
|
-
// concurrent sessions. commitFinalizeLogResult stays in-process
|
|
563
|
-
// because it touches the in-memory cache, SSE emitter, and
|
|
564
|
-
// append‑only log — all of which belong to the main thread.
|
|
565
|
-
|
|
566
|
-
// ── Routing ─────────────────────────────────────────────────────
|
|
567
|
-
// FINALIZER_RUNTIME selects the execution backend:
|
|
568
|
-
// "process" → per-session child process (max isolation)
|
|
569
|
-
// "worker" → shared Worker Thread pool
|
|
570
|
-
// "inline" → synchronous in-process (debug / fallback)
|
|
571
|
-
// The default is the bounded shared Worker pool when its entry exists. The
|
|
572
|
-
// per-session process backend remains an explicit isolation option only.
|
|
573
|
-
// For backward compatibility, FINALIZER_USE_WORKER=0 forces "inline".
|
|
574
|
-
|
|
575
|
-
const RUNTIME: "process" | "worker" | "inline" = (() => {
|
|
576
|
-
if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
|
|
577
|
-
const mode = process.env["FINALIZER_RUNTIME"];
|
|
578
|
-
if (mode === "process") return "process";
|
|
579
|
-
if (mode === "worker") return "worker";
|
|
580
|
-
if (mode === "inline") return "inline";
|
|
581
|
-
if (isLogFinalizerWorkerAvailable()) return "worker";
|
|
582
|
-
return isSessionProcessAvailable() ? "process" : "inline";
|
|
583
|
-
})();
|
|
584
|
-
|
|
585
|
-
function executeBuildInSessionProcess(job: FinalizeLogJob): Promise<FinalizeLogResult> {
|
|
586
|
-
const sessionId = job.log.sessionId ?? "__unassigned__";
|
|
587
|
-
return getSessionProcess(sessionId)
|
|
588
|
-
.enqueue(job)
|
|
589
|
-
.catch((err: unknown) => {
|
|
590
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
591
|
-
logger.error(
|
|
592
|
-
`[logFinalizer] Session process failed for log #${job.log.id}, ` +
|
|
593
|
-
`falling back to in-process: ${message}`,
|
|
594
|
-
);
|
|
595
|
-
return buildFinalizeLogResult(job);
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function resolveBuildPromise(job: FinalizeLogJob): Promise<FinalizeLogResult> {
|
|
600
|
-
switch (RUNTIME) {
|
|
601
|
-
case "process":
|
|
602
|
-
return executeBuildInSessionProcess(job);
|
|
603
|
-
case "worker":
|
|
604
|
-
return executeBuildInWorker(job);
|
|
605
|
-
case "inline":
|
|
606
|
-
return Promise.resolve(buildFinalizeLogResult(job));
|
|
607
|
-
default: {
|
|
608
|
-
const _exhaustive: never = RUNTIME;
|
|
609
|
-
logger.error(`[logFinalizer] Unhandled runtime: ${JSON.stringify(_exhaustive)}`);
|
|
610
|
-
return Promise.resolve({
|
|
611
|
-
log: job.log,
|
|
612
|
-
upstreamUrl: job.upstreamUrl,
|
|
613
|
-
error: "Unhandled runtime",
|
|
614
|
-
cleanupRawStreamPath: null,
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const WORKER_COUNT = Math.max(1, Number(process.env["FINALIZER_WORKER_COUNT"]) || 4);
|
|
621
|
-
let _workers: Worker[] | null = null;
|
|
622
|
-
const _pending = new Map<
|
|
623
|
-
string,
|
|
624
|
-
{ resolve: (result: FinalizeLogResult) => void; reject: (err: Error) => void }
|
|
625
|
-
>();
|
|
626
|
-
let _nextWorker = 0;
|
|
627
|
-
let _jobSeq = 0;
|
|
628
|
-
|
|
629
|
-
function resolveLogFinalizerWorkerUrl(): URL {
|
|
630
|
-
const workerDir = process.env["AGENT_INSPECTOR_WORKER_DIR"];
|
|
631
|
-
if (workerDir !== undefined && workerDir.trim().length > 0) {
|
|
632
|
-
const builtWorkerPath = join(workerDir, "logFinalizer.worker.js");
|
|
633
|
-
if (existsSync(builtWorkerPath)) return pathToFileURL(builtWorkerPath);
|
|
634
|
-
}
|
|
635
|
-
return new URL("./logFinalizer.worker.ts", import.meta.url);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
export function isLogFinalizerWorkerAvailable(): boolean {
|
|
639
|
-
return existsSync(fileURLToPath(resolveLogFinalizerWorkerUrl()));
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
export type FinalizerRuntimeHealth = {
|
|
643
|
-
runtime: "process" | "worker" | "inline";
|
|
644
|
-
artifactAvailable: boolean;
|
|
645
|
-
activeWorkers: number | null;
|
|
646
|
-
configuredWorkers: number;
|
|
647
|
-
pendingJobs: number;
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
/** Safe aggregate state used by readiness checks; it contains no job or session data. */
|
|
651
|
-
export function getFinalizerRuntimeHealth(): FinalizerRuntimeHealth {
|
|
652
|
-
const artifactAvailable =
|
|
653
|
-
RUNTIME === "worker"
|
|
654
|
-
? isLogFinalizerWorkerAvailable()
|
|
655
|
-
: RUNTIME === "process"
|
|
656
|
-
? isSessionProcessAvailable()
|
|
657
|
-
: isLogFinalizerWorkerAvailable() || isSessionProcessAvailable();
|
|
658
|
-
const activeWorkers =
|
|
659
|
-
_workers === null ? null : _workers.filter((worker) => worker.threadId > 0).length;
|
|
660
|
-
return {
|
|
661
|
-
runtime: RUNTIME,
|
|
662
|
-
artifactAvailable,
|
|
663
|
-
activeWorkers,
|
|
664
|
-
configuredWorkers: WORKER_COUNT,
|
|
665
|
-
pendingJobs: RUNTIME === "process" ? getSessionProcessPendingCount() : _pending.size,
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function getWorkers(): Worker[] {
|
|
670
|
-
if (_workers !== null) return _workers;
|
|
671
|
-
_workers = [];
|
|
672
|
-
for (let i = 0; i < WORKER_COUNT; i++) {
|
|
673
|
-
const w = new Worker(resolveLogFinalizerWorkerUrl());
|
|
674
|
-
w.on("message", (raw: unknown) => {
|
|
675
|
-
if (!isPlainRecord(raw)) {
|
|
676
|
-
logger.error("[logFinalizer] Received malformed IPC message from worker, discarding");
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
const id = safeGetOwnProperty(raw, "id");
|
|
680
|
-
const result = safeGetOwnProperty(raw, "result");
|
|
681
|
-
if (typeof id !== "string" || !isPlainRecord(result)) {
|
|
682
|
-
logger.error("[logFinalizer] Received malformed IPC message from worker, discarding");
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
// Runtime shape validated above; the cast bridges unknown → FinalizeLogResult.
|
|
686
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
687
|
-
const msg = { id, result: result as FinalizeLogResult };
|
|
688
|
-
const entry = _pending.get(msg.id);
|
|
689
|
-
if (entry !== undefined) {
|
|
690
|
-
_pending.delete(msg.id);
|
|
691
|
-
entry.resolve(msg.result);
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
w.on("error", (err) => {
|
|
695
|
-
logger.error(
|
|
696
|
-
"[logFinalizer] Worker error:",
|
|
697
|
-
err instanceof Error ? err.message : String(err),
|
|
698
|
-
);
|
|
699
|
-
// Reject all pending jobs for this worker so callers don't hang forever
|
|
700
|
-
for (const [id, entry] of _pending) {
|
|
701
|
-
_pending.delete(id);
|
|
702
|
-
entry.reject(
|
|
703
|
-
new Error(`Worker crashed: ${err instanceof Error ? err.message : String(err)}`),
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
});
|
|
707
|
-
_workers.push(w);
|
|
708
|
-
}
|
|
709
|
-
return _workers;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
export function executeBuildInWorker(job: FinalizeLogJob): Promise<FinalizeLogResult> {
|
|
713
|
-
return new Promise((resolve, reject) => {
|
|
714
|
-
const list = getWorkers();
|
|
715
|
-
// getWorkers() always returns at least 1 worker; the array index is safe
|
|
716
|
-
const idx = _nextWorker % list.length;
|
|
717
|
-
_nextWorker++;
|
|
718
|
-
const w = list[idx];
|
|
719
|
-
if (w === undefined) {
|
|
720
|
-
reject(new Error("No workers available"));
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
const id = String(++_jobSeq);
|
|
724
|
-
_pending.set(id, { resolve, reject });
|
|
725
|
-
w.postMessage({ id, job });
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export function executeFinalizeLogJob(job: FinalizeLogJob): Promise<void> {
|
|
730
|
-
const timeoutMs = resolveFinalizeJobTimeoutMs();
|
|
731
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
732
|
-
const buildPromise = resolveBuildPromise(job);
|
|
733
|
-
const timeoutPromise = new Promise<FinalizeLogResult>((resolve) => {
|
|
734
|
-
timeout = setTimeout(() => {
|
|
735
|
-
logger.error(
|
|
736
|
-
`[logFinalizer] Finalize job timed out for log #${job.log.id} after ${String(timeoutMs)}ms`,
|
|
737
|
-
);
|
|
738
|
-
resolve(buildFinalizeTimeoutResult(job, timeoutMs));
|
|
739
|
-
}, timeoutMs);
|
|
740
|
-
});
|
|
741
|
-
void buildPromise.catch((err: unknown) => {
|
|
742
|
-
logger.error(
|
|
743
|
-
`[logFinalizer] Late finalize build failure for log #${job.log.id}:`,
|
|
744
|
-
errorMessage(err),
|
|
745
|
-
);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
return Promise.race([buildPromise, timeoutPromise]).then(async (result) => {
|
|
749
|
-
if (timeout !== null) clearTimeout(timeout);
|
|
750
|
-
await commitFinalizeLogResult(result);
|
|
751
|
-
if (result.error !== null && result.error !== "Client aborted") {
|
|
752
|
-
return Promise.reject(new Error(result.error));
|
|
753
|
-
}
|
|
754
|
-
return undefined;
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function detachLogFinalizerWorkers(): Worker[] {
|
|
759
|
-
const workers = _workers ?? [];
|
|
760
|
-
_workers = null;
|
|
761
|
-
for (const [id, entry] of _pending) {
|
|
762
|
-
_pending.delete(id);
|
|
763
|
-
entry.reject(new Error("Log finalizer worker pool is shutting down"));
|
|
764
|
-
}
|
|
765
|
-
return workers;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/** Close the shared worker pool after queued session tasks have drained. */
|
|
769
|
-
export async function closeLogFinalizerWorkers(): Promise<void> {
|
|
770
|
-
const workers = detachLogFinalizerWorkers();
|
|
771
|
-
await Promise.all(workers.map((worker) => worker.terminate()));
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
/** Best-effort immediate worker teardown used when the shutdown deadline expires. */
|
|
775
|
-
export function forceCloseLogFinalizerWorkers(): void {
|
|
776
|
-
for (const worker of detachLogFinalizerWorkers()) void worker.terminate();
|
|
777
|
-
}
|