@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
package/src/lib/safeFetch.ts
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
import { lookup } from "node:dns/promises";
|
|
2
|
-
import { request as requestHttp } from "node:http";
|
|
3
|
-
import { request as requestHttps } from "node:https";
|
|
4
|
-
import { isIP } from "node:net";
|
|
5
|
-
import type { IncomingMessage, RequestOptions } from "node:http";
|
|
6
|
-
import { evaluateUpstreamUrl, isInternalHost, type SsrfGuardOptions } from "./ssrfGuard";
|
|
7
|
-
|
|
8
|
-
export type ResolvedAddress = {
|
|
9
|
-
address: string;
|
|
10
|
-
family: number;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type HostResolver = (hostname: string) => Promise<readonly ResolvedAddress[]>;
|
|
14
|
-
|
|
15
|
-
export type SafeFetchOptions = SsrfGuardOptions & {
|
|
16
|
-
resolver?: HostResolver;
|
|
17
|
-
deadlineMs?: number;
|
|
18
|
-
responseBytes?: number;
|
|
19
|
-
pinDns?: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type SafeFetchResult =
|
|
23
|
-
| { ok: true; response: Response }
|
|
24
|
-
| {
|
|
25
|
-
ok: false;
|
|
26
|
-
kind: "network-policy" | "redirect" | "resource-limit" | "timeout";
|
|
27
|
-
reason: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
async function defaultResolver(hostname: string): Promise<readonly ResolvedAddress[]> {
|
|
31
|
-
return lookup(hostname, { all: true, verbatim: true });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isLoopbackAddress(hostname: string): boolean {
|
|
35
|
-
const lower = hostname.toLowerCase();
|
|
36
|
-
return (
|
|
37
|
-
lower === "::1" ||
|
|
38
|
-
lower.startsWith("::ffff:127.") ||
|
|
39
|
-
lower.startsWith("::ffff:7f00:") ||
|
|
40
|
-
/^127\./.test(lower)
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function evaluateResolvedUpstreamUrl(
|
|
45
|
-
rawUrl: string,
|
|
46
|
-
options: SafeFetchOptions = {},
|
|
47
|
-
): Promise<{ allow: true } | { allow: false; reason: string }> {
|
|
48
|
-
const decision = await evaluateResolvedUpstreamUrlForFetch(rawUrl, options);
|
|
49
|
-
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type ResolvedUpstreamUrlDecision =
|
|
53
|
-
| { allow: true; hostname: string; addresses: readonly ResolvedAddress[] }
|
|
54
|
-
| { allow: false; reason: string };
|
|
55
|
-
|
|
56
|
-
async function evaluateResolvedUpstreamUrlForFetch(
|
|
57
|
-
rawUrl: string,
|
|
58
|
-
options: SafeFetchOptions,
|
|
59
|
-
): Promise<ResolvedUpstreamUrlDecision> {
|
|
60
|
-
const initial = evaluateUpstreamUrl(rawUrl, options);
|
|
61
|
-
if (!initial.allow) return initial;
|
|
62
|
-
|
|
63
|
-
const parsed = new URL(rawUrl);
|
|
64
|
-
const hostname = parsed.hostname.replace(/^\[|]$/g, "");
|
|
65
|
-
if (options.bypass === true) return { allow: true, hostname, addresses: [] };
|
|
66
|
-
|
|
67
|
-
const ipFamily = isIP(hostname);
|
|
68
|
-
if (ipFamily !== 0)
|
|
69
|
-
return { allow: true, hostname, addresses: [{ address: hostname, family: ipFamily }] };
|
|
70
|
-
|
|
71
|
-
let addresses: readonly ResolvedAddress[];
|
|
72
|
-
try {
|
|
73
|
-
addresses = await (options.resolver ?? defaultResolver)(hostname);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
return {
|
|
76
|
-
allow: false,
|
|
77
|
-
reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (addresses.length === 0) {
|
|
82
|
-
return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
for (const resolved of addresses) {
|
|
86
|
-
if (!isInternalHost(resolved.address)) continue;
|
|
87
|
-
if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
|
|
88
|
-
return {
|
|
89
|
-
allow: false,
|
|
90
|
-
reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { allow: true, hostname, addresses };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function isRedirectStatus(status: number): boolean {
|
|
98
|
-
return status >= 300 && status < 400;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function formatByteLimit(limit: number): string {
|
|
102
|
-
return `${String(limit)} bytes`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function parseContentLength(value: string | null): number | null {
|
|
106
|
-
if (value === null) return null;
|
|
107
|
-
const parsed = Number(value);
|
|
108
|
-
if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
|
|
109
|
-
return parsed;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function isPinnedDnsEnabled(options: SafeFetchOptions): boolean {
|
|
113
|
-
const disabledForTest =
|
|
114
|
-
typeof process !== "undefined" &&
|
|
115
|
-
process.env["NODE_ENV"] === "test" &&
|
|
116
|
-
process.env["AGENT_INSPECTOR_DISABLE_DNS_PINNING_FOR_TESTS"] === "1";
|
|
117
|
-
if (disabledForTest) return false;
|
|
118
|
-
return options.pinDns !== false && options.bypass !== true;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function choosePinnedAddress(addresses: readonly ResolvedAddress[]): ResolvedAddress | null {
|
|
122
|
-
return addresses[0] ?? null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function hostHeaderValue(parsed: URL): string {
|
|
126
|
-
return parsed.port.length === 0 ? parsed.host : `${parsed.hostname}:${parsed.port}`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function headersToRecord(headers: HeadersInit | undefined, parsed: URL): Record<string, string> {
|
|
130
|
-
const normalized = new Headers(headers);
|
|
131
|
-
if (!normalized.has("host")) {
|
|
132
|
-
normalized.set("host", hostHeaderValue(parsed));
|
|
133
|
-
}
|
|
134
|
-
const result: Record<string, string> = {};
|
|
135
|
-
normalized.forEach((value, key) => {
|
|
136
|
-
result[key] = value;
|
|
137
|
-
});
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function requestBodyToUint8Array(
|
|
142
|
-
body: BodyInit | null | undefined,
|
|
143
|
-
): Promise<
|
|
144
|
-
{ ok: true; body: Uint8Array | null; contentType: string | null } | { ok: false; reason: string }
|
|
145
|
-
> {
|
|
146
|
-
if (body === undefined || body === null) return { ok: true, body: null, contentType: null };
|
|
147
|
-
if (typeof body === "string") {
|
|
148
|
-
return { ok: true, body: new TextEncoder().encode(body), contentType: null };
|
|
149
|
-
}
|
|
150
|
-
if (body instanceof URLSearchParams) {
|
|
151
|
-
return {
|
|
152
|
-
ok: true,
|
|
153
|
-
body: new TextEncoder().encode(body.toString()),
|
|
154
|
-
contentType: "application/x-www-form-urlencoded;charset=UTF-8",
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
if (body instanceof Uint8Array) return { ok: true, body, contentType: null };
|
|
158
|
-
if (body instanceof ArrayBuffer) {
|
|
159
|
-
return { ok: true, body: new Uint8Array(body), contentType: null };
|
|
160
|
-
}
|
|
161
|
-
if (body instanceof Blob) {
|
|
162
|
-
return { ok: true, body: new Uint8Array(await body.arrayBuffer()), contentType: body.type };
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
ok: false,
|
|
166
|
-
reason: "Pinned upstream fetch only supports buffered request bodies",
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function pinnedLookup(
|
|
171
|
-
pinnedAddress: ResolvedAddress,
|
|
172
|
-
): (
|
|
173
|
-
hostname: string,
|
|
174
|
-
options: { all?: boolean },
|
|
175
|
-
callback: (error: Error | null, address: string | ResolvedAddress[], family?: number) => void,
|
|
176
|
-
) => void {
|
|
177
|
-
return (_hostname, options, callback) => {
|
|
178
|
-
if (options.all === true) {
|
|
179
|
-
callback(null, [pinnedAddress]);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
callback(null, pinnedAddress.address, pinnedAddress.family);
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function requestOptionsForPinnedFetch(
|
|
187
|
-
parsed: URL,
|
|
188
|
-
init: RequestInit,
|
|
189
|
-
pinnedAddress: ResolvedAddress,
|
|
190
|
-
contentType: string | null,
|
|
191
|
-
): RequestOptions {
|
|
192
|
-
const headers = headersToRecord(init.headers, parsed);
|
|
193
|
-
if (contentType !== null && !new Headers(headers).has("content-type")) {
|
|
194
|
-
headers["content-type"] = contentType;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
protocol: parsed.protocol,
|
|
199
|
-
hostname: parsed.hostname,
|
|
200
|
-
port: parsed.port,
|
|
201
|
-
path: `${parsed.pathname}${parsed.search}`,
|
|
202
|
-
method: init.method ?? "GET",
|
|
203
|
-
headers,
|
|
204
|
-
lookup: pinnedLookup(pinnedAddress),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function streamFromIncomingMessage(
|
|
209
|
-
incoming: IncomingMessage,
|
|
210
|
-
maxBytes: number | undefined,
|
|
211
|
-
): ReadableStream<Uint8Array> {
|
|
212
|
-
let seenBytes = 0;
|
|
213
|
-
return new ReadableStream<Uint8Array>({
|
|
214
|
-
start(controller) {
|
|
215
|
-
incoming.on("data", (chunk: Uint8Array) => {
|
|
216
|
-
seenBytes += chunk.byteLength;
|
|
217
|
-
if (maxBytes !== undefined && seenBytes > maxBytes) {
|
|
218
|
-
incoming.destroy(
|
|
219
|
-
new Error(
|
|
220
|
-
`Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`,
|
|
221
|
-
),
|
|
222
|
-
);
|
|
223
|
-
controller.error(
|
|
224
|
-
new Error(
|
|
225
|
-
`Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`,
|
|
226
|
-
),
|
|
227
|
-
);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
controller.enqueue(chunk);
|
|
231
|
-
});
|
|
232
|
-
incoming.on("end", () => {
|
|
233
|
-
controller.close();
|
|
234
|
-
});
|
|
235
|
-
incoming.on("error", (error) => {
|
|
236
|
-
controller.error(error);
|
|
237
|
-
});
|
|
238
|
-
},
|
|
239
|
-
cancel() {
|
|
240
|
-
incoming.destroy();
|
|
241
|
-
},
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async function cancelBody(response: Response): Promise<void> {
|
|
246
|
-
if (response.body === null) return;
|
|
247
|
-
await response.body.cancel().catch(() => undefined);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function responseWithByteLimit(response: Response, maxBytes: number): Response {
|
|
251
|
-
if (response.body === null) return response;
|
|
252
|
-
|
|
253
|
-
let seenBytes = 0;
|
|
254
|
-
const reader = response.body.getReader();
|
|
255
|
-
const body = new ReadableStream<Uint8Array>({
|
|
256
|
-
async pull(controller) {
|
|
257
|
-
const chunk = await reader.read();
|
|
258
|
-
if (chunk.done) {
|
|
259
|
-
controller.close();
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
seenBytes += chunk.value.byteLength;
|
|
264
|
-
if (seenBytes > maxBytes) {
|
|
265
|
-
void reader.cancel().catch(() => undefined);
|
|
266
|
-
controller.error(
|
|
267
|
-
new Error(`Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`),
|
|
268
|
-
);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
controller.enqueue(chunk.value);
|
|
273
|
-
},
|
|
274
|
-
cancel(reason) {
|
|
275
|
-
return reader.cancel(reason);
|
|
276
|
-
},
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
return new Response(body, {
|
|
280
|
-
status: response.status,
|
|
281
|
-
statusText: response.statusText,
|
|
282
|
-
headers: response.headers,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function shouldUseBudgetedSignal(init: RequestInit, deadlineMs: number | undefined): boolean {
|
|
287
|
-
return deadlineMs !== undefined || init.signal !== undefined;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function pinnedNodeFetch(
|
|
291
|
-
rawUrl: string,
|
|
292
|
-
init: RequestInit,
|
|
293
|
-
options: SafeFetchOptions,
|
|
294
|
-
decision: Extract<ResolvedUpstreamUrlDecision, { allow: true }>,
|
|
295
|
-
): Promise<SafeFetchResult> {
|
|
296
|
-
const parsed = new URL(rawUrl);
|
|
297
|
-
const pinnedAddress = choosePinnedAddress(decision.addresses);
|
|
298
|
-
if (pinnedAddress === null) {
|
|
299
|
-
return {
|
|
300
|
-
ok: false,
|
|
301
|
-
kind: "network-policy",
|
|
302
|
-
reason: `Could not pin a resolved upstream address for "${decision.hostname}"`,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const encodedBody = await requestBodyToUint8Array(init.body);
|
|
307
|
-
if (!encodedBody.ok) {
|
|
308
|
-
return { ok: false, kind: "resource-limit", reason: encodedBody.reason };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return await new Promise<SafeFetchResult>((resolve, reject) => {
|
|
312
|
-
const linkedSignal = init.signal ?? undefined;
|
|
313
|
-
const transport = parsed.protocol === "https:" ? requestHttps : requestHttp;
|
|
314
|
-
const requestOptions = requestOptionsForPinnedFetch(
|
|
315
|
-
parsed,
|
|
316
|
-
init,
|
|
317
|
-
pinnedAddress,
|
|
318
|
-
encodedBody.contentType,
|
|
319
|
-
);
|
|
320
|
-
const request = transport(requestOptions, (incoming) => {
|
|
321
|
-
const status = incoming.statusCode ?? 0;
|
|
322
|
-
if (isRedirectStatus(status)) {
|
|
323
|
-
incoming.resume();
|
|
324
|
-
const location = incoming.headers.location;
|
|
325
|
-
resolve({
|
|
326
|
-
ok: false,
|
|
327
|
-
kind: "redirect",
|
|
328
|
-
reason:
|
|
329
|
-
location === undefined
|
|
330
|
-
? `Upstream returned blocked redirect HTTP ${String(status)}`
|
|
331
|
-
: `Upstream redirect to "${location}" is blocked; configure the final API URL directly`,
|
|
332
|
-
});
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const maxBytes = options.responseBytes;
|
|
337
|
-
const headers = new Headers();
|
|
338
|
-
for (const [key, value] of Object.entries(incoming.headers)) {
|
|
339
|
-
if (Array.isArray(value)) {
|
|
340
|
-
headers.set(key, value.join(", "));
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
if (value !== undefined) headers.set(key, value);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (maxBytes !== undefined) {
|
|
347
|
-
const declaredBytes = parseContentLength(headers.get("content-length"));
|
|
348
|
-
if (declaredBytes !== null && declaredBytes > maxBytes) {
|
|
349
|
-
incoming.destroy();
|
|
350
|
-
resolve({
|
|
351
|
-
ok: false,
|
|
352
|
-
kind: "resource-limit",
|
|
353
|
-
reason: `Upstream declared ${String(declaredBytes)} response bytes, exceeding configured limit of ${formatByteLimit(maxBytes)}`,
|
|
354
|
-
});
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
resolve({
|
|
360
|
-
ok: true,
|
|
361
|
-
response: new Response(streamFromIncomingMessage(incoming, maxBytes), {
|
|
362
|
-
status,
|
|
363
|
-
statusText: incoming.statusMessage,
|
|
364
|
-
headers,
|
|
365
|
-
}),
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
let timedOut = false;
|
|
370
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
371
|
-
const finishAbort = (): void => {
|
|
372
|
-
request.destroy(new DOMException("Safe fetch aborted", "AbortError"));
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
if (linkedSignal !== undefined) {
|
|
376
|
-
if (linkedSignal.aborted) {
|
|
377
|
-
finishAbort();
|
|
378
|
-
} else {
|
|
379
|
-
linkedSignal.addEventListener("abort", finishAbort, { once: true });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (options.deadlineMs !== undefined) {
|
|
384
|
-
timeout = setTimeout(() => {
|
|
385
|
-
timedOut = true;
|
|
386
|
-
request.destroy(new DOMException("Safe fetch deadline exceeded", "TimeoutError"));
|
|
387
|
-
}, options.deadlineMs);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
request.on("error", (error) => {
|
|
391
|
-
if (timeout !== undefined) clearTimeout(timeout);
|
|
392
|
-
if (linkedSignal !== undefined) {
|
|
393
|
-
linkedSignal.removeEventListener("abort", finishAbort);
|
|
394
|
-
}
|
|
395
|
-
if (timedOut) {
|
|
396
|
-
resolve({
|
|
397
|
-
ok: false,
|
|
398
|
-
kind: "timeout",
|
|
399
|
-
reason: `Upstream request exceeded configured deadline of ${String(options.deadlineMs)} ms`,
|
|
400
|
-
});
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
reject(error);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
request.on("close", () => {
|
|
407
|
-
if (timeout !== undefined) clearTimeout(timeout);
|
|
408
|
-
if (linkedSignal !== undefined) {
|
|
409
|
-
linkedSignal.removeEventListener("abort", finishAbort);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
if (encodedBody.body !== null) request.write(encodedBody.body);
|
|
414
|
-
request.end();
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
export async function safeFetch(
|
|
419
|
-
rawUrl: string,
|
|
420
|
-
init: RequestInit = {},
|
|
421
|
-
options: SafeFetchOptions = {},
|
|
422
|
-
): Promise<SafeFetchResult> {
|
|
423
|
-
const resolvedDecision = await evaluateResolvedUpstreamUrlForFetch(rawUrl, options);
|
|
424
|
-
if (!resolvedDecision.allow) {
|
|
425
|
-
return { ok: false, kind: "network-policy", reason: resolvedDecision.reason };
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (isPinnedDnsEnabled(options)) {
|
|
429
|
-
return await pinnedNodeFetch(rawUrl, init, options, resolvedDecision);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const deadlineMs = options.deadlineMs;
|
|
433
|
-
const useBudgetedSignal = shouldUseBudgetedSignal(init, deadlineMs);
|
|
434
|
-
const controller = useBudgetedSignal ? new AbortController() : undefined;
|
|
435
|
-
let timedOut = false;
|
|
436
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
437
|
-
const linkedSignal = init.signal ?? undefined;
|
|
438
|
-
const forwardAbort = (): void => {
|
|
439
|
-
controller?.abort(linkedSignal?.reason);
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
if (controller !== undefined && linkedSignal !== undefined) {
|
|
443
|
-
if (linkedSignal.aborted) {
|
|
444
|
-
forwardAbort();
|
|
445
|
-
} else {
|
|
446
|
-
linkedSignal.addEventListener("abort", forwardAbort, { once: true });
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (controller !== undefined && deadlineMs !== undefined) {
|
|
451
|
-
timeout = setTimeout(() => {
|
|
452
|
-
timedOut = true;
|
|
453
|
-
controller.abort(new DOMException("Safe fetch deadline exceeded", "TimeoutError"));
|
|
454
|
-
}, deadlineMs);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
const response = await fetch(rawUrl, {
|
|
459
|
-
...init,
|
|
460
|
-
redirect: "manual",
|
|
461
|
-
signal: controller?.signal ?? init.signal,
|
|
462
|
-
});
|
|
463
|
-
if (!isRedirectStatus(response.status)) {
|
|
464
|
-
const maxBytes = options.responseBytes;
|
|
465
|
-
if (maxBytes === undefined) return { ok: true, response };
|
|
466
|
-
|
|
467
|
-
const declaredBytes = parseContentLength(response.headers.get("content-length"));
|
|
468
|
-
if (declaredBytes !== null && declaredBytes > maxBytes) {
|
|
469
|
-
await cancelBody(response);
|
|
470
|
-
return {
|
|
471
|
-
ok: false,
|
|
472
|
-
kind: "resource-limit",
|
|
473
|
-
reason: `Upstream declared ${String(declaredBytes)} response bytes, exceeding configured limit of ${formatByteLimit(maxBytes)}`,
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
return { ok: true, response: responseWithByteLimit(response, maxBytes) };
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
await cancelBody(response);
|
|
481
|
-
const location = response.headers.get("location");
|
|
482
|
-
return {
|
|
483
|
-
ok: false,
|
|
484
|
-
kind: "redirect",
|
|
485
|
-
reason:
|
|
486
|
-
location === null
|
|
487
|
-
? `Upstream returned blocked redirect HTTP ${String(response.status)}`
|
|
488
|
-
: `Upstream redirect to "${location}" is blocked; configure the final API URL directly`,
|
|
489
|
-
};
|
|
490
|
-
} catch (error) {
|
|
491
|
-
if (timedOut) {
|
|
492
|
-
return {
|
|
493
|
-
ok: false,
|
|
494
|
-
kind: "timeout",
|
|
495
|
-
reason:
|
|
496
|
-
deadlineMs === undefined
|
|
497
|
-
? "Upstream request exceeded its configured deadline"
|
|
498
|
-
: `Upstream request exceeded configured deadline of ${String(deadlineMs)} ms`,
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
return Promise.reject(error);
|
|
502
|
-
} finally {
|
|
503
|
-
if (timeout !== undefined) clearTimeout(timeout);
|
|
504
|
-
if (controller !== undefined && linkedSignal !== undefined) {
|
|
505
|
-
linkedSignal.removeEventListener("abort", forwardAbort);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
package/src/lib/serverPort.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/* eslint-disable functional/no-throw-statements */
|
|
2
|
-
/**
|
|
3
|
-
* Tracks the HTTP port the inspector server is bound to, so internal
|
|
4
|
-
* components (currently the MCP server's loopback fetch) can build
|
|
5
|
-
* `http://127.0.0.1:<port>/...` URLs without env-var coupling.
|
|
6
|
-
*
|
|
7
|
-
* Initialization order:
|
|
8
|
-
* 1. `setCurrentPort(port)` — explicit override (used by tests, or any
|
|
9
|
-
* code that has direct knowledge of the bound port).
|
|
10
|
-
* 2. `process.env.AGENT_INSPECTOR_PUBLIC_PORT` — the public identity-proxy
|
|
11
|
-
* port. The CLI sets this when the runtime is hidden behind a private
|
|
12
|
-
* upstream port.
|
|
13
|
-
* 3. `process.env.PORT` — fallback for direct dev/server runs.
|
|
14
|
-
*
|
|
15
|
-
* `getCurrentPort()` throws if neither source is available.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
let overridePort: number | null = null;
|
|
19
|
-
|
|
20
|
-
export function setCurrentPort(port: number): void {
|
|
21
|
-
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
22
|
-
throw new Error(`setCurrentPort: invalid port ${port}`);
|
|
23
|
-
}
|
|
24
|
-
overridePort = port;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getCurrentPort(): number {
|
|
28
|
-
if (overridePort !== null) return overridePort;
|
|
29
|
-
const publicPort = process.env["AGENT_INSPECTOR_PUBLIC_PORT"];
|
|
30
|
-
if (publicPort !== undefined && publicPort !== "") {
|
|
31
|
-
const n = Number(publicPort);
|
|
32
|
-
if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
|
|
33
|
-
}
|
|
34
|
-
const envPort = process.env["PORT"];
|
|
35
|
-
if (envPort !== undefined && envPort !== "") {
|
|
36
|
-
const n = Number(envPort);
|
|
37
|
-
if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
|
|
38
|
-
}
|
|
39
|
-
throw new Error(
|
|
40
|
-
"Inspector server port not initialized: PORT env var is unset and setCurrentPort() has not been called",
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Reset for tests. */
|
|
45
|
-
export function _resetForTests(): void {
|
|
46
|
-
overridePort = null;
|
|
47
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { LogIdSchema } from "../contracts";
|
|
3
|
-
|
|
4
|
-
export const SessionTokenUsageSchema = z.object({
|
|
5
|
-
input: z.number().int().nonnegative(),
|
|
6
|
-
output: z.number().int().nonnegative(),
|
|
7
|
-
cacheCreate: z.number().int().nonnegative(),
|
|
8
|
-
cacheRead: z.number().int().nonnegative(),
|
|
9
|
-
total: z.number().int().nonnegative(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export const SessionLogSummarySchema = z.object({
|
|
13
|
-
id: LogIdSchema,
|
|
14
|
-
sessionLogNumber: z.number().int().positive().optional(),
|
|
15
|
-
timestamp: z.string(),
|
|
16
|
-
provider: z.string().nullable(),
|
|
17
|
-
model: z.string().nullable(),
|
|
18
|
-
apiFormat: z.enum(["anthropic", "openai", "unknown"]),
|
|
19
|
-
method: z.string(),
|
|
20
|
-
path: z.string(),
|
|
21
|
-
status: z.number().nullable(),
|
|
22
|
-
isStreaming: z.boolean(),
|
|
23
|
-
hasError: z.boolean(),
|
|
24
|
-
error: z.string().nullable(),
|
|
25
|
-
latencyMs: z.number().nullable(),
|
|
26
|
-
firstChunkMs: z.number().nullable().optional(),
|
|
27
|
-
totalStreamMs: z.number().nullable().optional(),
|
|
28
|
-
tokensPerSecond: z.number().nullable().optional(),
|
|
29
|
-
tokens: z.object({
|
|
30
|
-
input: z.number().nullable(),
|
|
31
|
-
output: z.number().nullable(),
|
|
32
|
-
cacheCreate: z.number().nullable(),
|
|
33
|
-
cacheRead: z.number().nullable(),
|
|
34
|
-
}),
|
|
35
|
-
sessionId: z.string().nullable(),
|
|
36
|
-
userAgent: z.string().nullable().optional(),
|
|
37
|
-
clientPid: z.number().nullable().optional(),
|
|
38
|
-
clientProjectFolder: z.string().nullable().optional(),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
export const SessionInfoSchema = z.object({
|
|
42
|
-
id: z.string(),
|
|
43
|
-
status: z.enum(["active", "failed", "completed", "empty"]),
|
|
44
|
-
logCoverage: z.enum(["recent", "history"]),
|
|
45
|
-
source: z.enum(["explicit", "client-process", "client-connection", "provider-test"]).nullable(),
|
|
46
|
-
runtimeMode: z.enum(["in-process", "worker-thread", "child-process"]).nullable(),
|
|
47
|
-
createdAt: z.string().nullable(),
|
|
48
|
-
updatedAt: z.string().nullable(),
|
|
49
|
-
requestCount: z.number().int().nonnegative(),
|
|
50
|
-
activeRequests: z.number().int().nonnegative(),
|
|
51
|
-
completedRequests: z.number().int().nonnegative(),
|
|
52
|
-
failedRequests: z.number().int().nonnegative(),
|
|
53
|
-
queuedTasks: z.number().int().nonnegative(),
|
|
54
|
-
runningTasks: z.number().int().nonnegative(),
|
|
55
|
-
lastTaskError: z.string().nullable(),
|
|
56
|
-
logCount: z.number().int().nonnegative(),
|
|
57
|
-
errorCount: z.number().int().nonnegative(),
|
|
58
|
-
models: z.array(z.string()),
|
|
59
|
-
providers: z.array(z.string()),
|
|
60
|
-
tokenUsage: SessionTokenUsageSchema,
|
|
61
|
-
lastLogId: LogIdSchema.nullable(),
|
|
62
|
-
lastModel: z.string().nullable(),
|
|
63
|
-
clientUserAgent: z.string().nullable().optional(),
|
|
64
|
-
clientPid: z.number().nullable(),
|
|
65
|
-
clientProjectFolder: z.string().nullable(),
|
|
66
|
-
inspectorPath: z.string(),
|
|
67
|
-
inspectorUrl: z.string(),
|
|
68
|
-
apiPath: z.string(),
|
|
69
|
-
apiUrl: z.string(),
|
|
70
|
-
compactLogsPath: z.string(),
|
|
71
|
-
compactLogsUrl: z.string(),
|
|
72
|
-
latestLogLimit: z.number().int().positive(),
|
|
73
|
-
latestLogs: z.array(SessionLogSummarySchema),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
export type SessionTokenUsage = z.infer<typeof SessionTokenUsageSchema>;
|
|
77
|
-
export type SessionLogSummary = z.infer<typeof SessionLogSummarySchema>;
|
|
78
|
-
export type SessionInfo = z.infer<typeof SessionInfoSchema>;
|
package/src/lib/sessionUrl.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { withBasePath } from "./basePath";
|
|
2
|
-
|
|
3
|
-
const B64URL_RE = /^[A-Za-z0-9_-]+$/;
|
|
4
|
-
|
|
5
|
-
function bytesToBinary(bytes: Uint8Array): string {
|
|
6
|
-
let binary = "";
|
|
7
|
-
for (const byte of bytes) {
|
|
8
|
-
binary += String.fromCharCode(byte);
|
|
9
|
-
}
|
|
10
|
-
return binary;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function binaryToBytes(binary: string): Uint8Array {
|
|
14
|
-
const bytes = new Uint8Array(binary.length);
|
|
15
|
-
for (let i = 0; i < binary.length; i++) {
|
|
16
|
-
bytes[i] = binary.charCodeAt(i);
|
|
17
|
-
}
|
|
18
|
-
return bytes;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function encodeSessionIdForPath(sessionId: string): string {
|
|
22
|
-
const bytes = new TextEncoder().encode(sessionId);
|
|
23
|
-
const base64 = btoa(bytesToBinary(bytes));
|
|
24
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function decodeSessionIdFromPath(encoded: string): string {
|
|
28
|
-
if (encoded.startsWith("%")) {
|
|
29
|
-
return decodeURIComponent(encoded);
|
|
30
|
-
}
|
|
31
|
-
if (!B64URL_RE.test(encoded)) {
|
|
32
|
-
return encoded;
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const padded = encoded.padEnd(Math.ceil(encoded.length / 4) * 4, "=");
|
|
36
|
-
const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
37
|
-
const binary = atob(base64);
|
|
38
|
-
return new TextDecoder().decode(binaryToBytes(binary));
|
|
39
|
-
} catch {
|
|
40
|
-
return encoded;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function getSessionPath(sessionId: string, basePath?: string): string {
|
|
45
|
-
return withBasePath(`/session/${encodeSessionIdForPath(sessionId)}`, basePath);
|
|
46
|
-
}
|