@tonyclaw/agent-inspector 3.0.45 → 3.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/{nitro.json → backend/nitro.json} +3 -3
- package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
- package/.output/cli.js +8018 -5451
- package/.output/server/_libs/extend.mjs +4 -8
- package/.output/server/_libs/jszip.mjs +9 -35
- package/.output/server/_libs/react-dom.mjs +2 -3
- package/.output/server/_libs/react.mjs +1 -27
- package/.output/server/_libs/tanstack__react-router.mjs +1 -329
- package/.output/server/_libs/tanstack__router-core.mjs +42 -100
- package/.output/server/_libs/use-sync-external-store.mjs +1 -64
- package/.output/server/_libs/ws.mjs +1 -1
- package/.output/server/_ssr/index.mjs +4 -4
- package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
- package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-BwZEsYfS.mjs} +19794 -19444
- package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
- package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
- package/.output/server/index.mjs +5 -145
- package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-V-8lPASC.js} +1 -1
- package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-D4CVnfdf.js} +1 -1
- package/.output/ui/assets/ProxyViewerContainer-CO3ZVHLX.js +59 -0
- package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-CpGyTLtt.js} +1 -1
- package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-BLuJL9dZ.js} +1 -1
- package/.output/ui/assets/ResponseView-BuSC-CH7.js +2 -0
- package/.output/ui/assets/StreamingChunkSequence-DeA-JJKa.js +1 -0
- package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-8HuHFhjo.js} +1 -1
- package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-Chp67th6.js} +1 -1
- package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
- package/.output/ui/assets/index-BvXp42al.css +1 -0
- package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-DPmKK_5d.js} +5 -5
- package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-atoQrlb8.js} +1 -1
- package/.output/ui/assets/index-nY3E1vxV.js +73 -0
- package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-nl9-dpZn.js} +1 -1
- package/.output/ui/assets/json-viewer-B9kIzqeX.js +1 -0
- package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B7tfyE-I.js} +1 -1
- package/.output/ui/index.html +229 -0
- package/.output/ui/runtime-config.js +2 -0
- package/.output/workers/logFinalizer.worker.js +979 -414
- package/.output/workers/sessionWorkerEntry.js +979 -414
- package/README.md +182 -43
- package/docs/instance-control.openapi.yaml +59 -4
- package/package.json +40 -6
- package/scripts/generate-theme-scales.mjs +3 -3
- package/src/{router.tsx → backend/router.tsx} +3 -15
- package/src/backend/routes/$.ts +26 -0
- package/src/backend/routes/__root.ts +4 -0
- package/src/backend/routes/api/-instances.ts +7 -0
- package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
- package/src/{routes → backend/routes}/api/alerts.ts +4 -4
- package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
- package/src/{routes → backend/routes}/api/config.ts +11 -2
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
- package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
- package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.ts +2 -2
- package/src/{routes → backend/routes}/api/health.ts +3 -3
- package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
- package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
- package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
- package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
- package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
- package/src/{routes → backend/routes}/api/logs.ts +17 -9
- package/src/{routes → backend/routes}/api/mcp.ts +1 -1
- package/src/{routes → backend/routes}/api/models.ts +1 -1
- package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
- package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
- package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
- package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
- package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
- package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.ts +9 -4
- package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.ts +4 -4
- package/src/backend/routes/api/runtime.ts +46 -0
- package/src/{routes → backend/routes}/api/sessions.ts +3 -3
- package/src/{routes → backend/routes}/proxy/$.ts +1 -1
- package/src/{routes → backend/routes}/readyz.ts +1 -1
- package/src/{start.ts → backend/start.ts} +1 -1
- package/src/cli/alias.ts +2 -11
- package/src/cli/doctor.ts +32 -9
- package/src/cli/instance.ts +36 -13
- package/src/cli/instanceApi.ts +213 -0
- package/src/cli/instanceArgs.ts +46 -11
- package/src/cli/instanceControl.ts +10 -2
- package/src/cli/instanceModel.ts +18 -17
- package/src/cli/instanceRegistry.ts +0 -9
- package/src/cli/networkHints.ts +27 -14
- package/src/cli/onboard.ts +69 -2
- package/src/cli/onboardTarget.ts +89 -40
- package/src/cli/rustBackendPackage.ts +450 -0
- package/src/cli/templates/codex-skill-onboard.ts +44 -14
- package/src/cli/templates/skill-onboard.ts +64 -28
- package/src/cli.ts +341 -75
- package/src/components/ProxyViewer.tsx +11 -9
- package/src/components/ProxyViewerContainer.tsx +12 -11
- package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
- package/src/components/providers/ProviderCard.tsx +1 -1
- package/src/components/providers/ProviderTestResultsView.tsx +1 -1
- package/src/components/providers/SettingsDialog.tsx +159 -144
- package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
- package/src/components/proxy-viewer/LogEntry.tsx +1 -1
- package/src/components/proxy-viewer/log-formats/index.ts +1 -1
- package/src/contracts/index.ts +5 -0
- package/src/contracts/log.ts +33 -3
- package/src/knowledge/types.ts +3 -2
- package/src/lib/alertContract.ts +2 -2
- package/src/lib/apiClient.ts +30 -7
- package/src/lib/apiFormat.ts +29 -0
- package/src/lib/backendImplementation.ts +16 -0
- package/src/lib/basePath.ts +29 -11
- package/src/lib/browserRuntimeContract.ts +124 -0
- package/src/lib/browserRuntimeResolver.ts +244 -0
- package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
- package/src/lib/codingAgentEndpoints.ts +38 -0
- package/src/lib/instanceContract.ts +30 -11
- package/src/lib/managedInstance.ts +9 -1
- package/src/lib/piAgentContract.ts +3 -2
- package/src/lib/publicBasePathContext.ts +40 -10
- package/src/lib/resourceLimits.ts +2 -0
- package/src/lib/runContract.ts +3 -3
- package/src/lib/sessionInfoContract.ts +3 -2
- package/src/mcp/instanceHandlers.ts +2 -0
- package/src/mcp/server.ts +19 -24
- package/src/proxy/config.ts +25 -30
- package/src/proxy/dataDir.ts +43 -7
- package/src/proxy/ecosystemTasks.ts +161 -70
- package/src/proxy/formats/registry.ts +6 -21
- package/src/proxy/handler.ts +129 -29
- package/src/proxy/identityProxy.ts +567 -39
- package/src/proxy/jsonlRecovery.ts +554 -0
- package/src/proxy/logFinalizer.ts +25 -10
- package/src/proxy/logImporter.ts +2 -1
- package/src/proxy/logIndex.ts +191 -95
- package/src/proxy/logger.ts +77 -14
- package/src/proxy/platformCommands.ts +41 -0
- package/src/proxy/privateDataPath.ts +310 -88
- package/src/proxy/rawStreamCapture.ts +194 -13
- package/src/proxy/runtimeAdmission.ts +5 -2
- package/src/proxy/runtimeHealth.ts +14 -1
- package/src/proxy/runtimeShutdown.ts +11 -1
- package/src/proxy/schemas.ts +7 -1
- package/src/proxy/sessionArchive.ts +70 -14
- package/src/proxy/sqliteLogIndex.ts +65 -42
- package/src/proxy/store.ts +141 -34
- package/src/proxy/uiAssetServer.ts +106 -0
- package/src/proxy/upstream.ts +29 -2
- package/src/ui/bootstrap.ts +167 -0
- package/src/ui/index.html +38 -0
- package/src/ui/logsStreamClient.ts +20 -0
- package/src/ui/main.tsx +13 -0
- package/src/ui/public/runtime-config.js +2 -0
- package/src/ui/router.tsx +13 -0
- package/src/ui/routes/__root.tsx +69 -0
- package/src/{routes → ui/routes}/index.tsx +2 -3
- package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
- package/src/ui/runtimeDiscovery.ts +50 -0
- package/styles/globals.css +1 -0
- package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
- package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
- package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
- package/.output/public/assets/index-BZo_8koU.js +0 -70
- package/.output/public/assets/index-ZWschkCh.css +0 -1
- package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
- package/.output/server/_libs/aria-hidden.mjs +0 -122
- package/.output/server/_libs/bail.mjs +0 -8
- package/.output/server/_libs/character-entities.mjs +0 -2130
- package/.output/server/_libs/class-variance-authority.mjs +0 -44
- package/.output/server/_libs/clsx.mjs +0 -16
- package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
- package/.output/server/_libs/dequal.mjs +0 -27
- package/.output/server/_libs/detect-node-es.mjs +0 -1
- package/.output/server/_libs/devlop.mjs +0 -8
- package/.output/server/_libs/diff.mjs +0 -320
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
- package/.output/server/_libs/floating-ui__core.mjs +0 -725
- package/.output/server/_libs/floating-ui__dom.mjs +0 -622
- package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
- package/.output/server/_libs/floating-ui__utils.mjs +0 -320
- package/.output/server/_libs/get-nonce.mjs +0 -9
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
- package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
- package/.output/server/_libs/html-url-attributes.mjs +0 -26
- package/.output/server/_libs/inline-style-parser.mjs +0 -142
- package/.output/server/_libs/is-plain-obj.mjs +0 -10
- package/.output/server/_libs/lucide-react.mjs +0 -1027
- package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
- package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
- package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
- package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
- package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
- package/.output/server/_libs/micromark-factory-label.mjs +0 -63
- package/.output/server/_libs/micromark-factory-space.mjs +0 -24
- package/.output/server/_libs/micromark-factory-title.mjs +0 -65
- package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
- package/.output/server/_libs/micromark-util-character.mjs +0 -44
- package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
- package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
- package/.output/server/_libs/micromark-util-encode.mjs +0 -1
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
- package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
- package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
- package/.output/server/_libs/micromark.mjs +0 -906
- package/.output/server/_libs/property-information.mjs +0 -1209
- package/.output/server/_libs/radix-ui.mjs +0 -1
- package/.output/server/_libs/radix-ui__number.mjs +0 -6
- package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
- package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
- package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
- package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
- package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
- package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
- package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
- package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
- package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
- package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
- package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
- package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
- package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
- package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
- package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
- package/.output/server/_libs/react-markdown.mjs +0 -235
- package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
- package/.output/server/_libs/react-remove-scroll.mjs +0 -328
- package/.output/server/_libs/react-style-singleton.mjs +0 -69
- package/.output/server/_libs/remark-parse.mjs +0 -19
- package/.output/server/_libs/remark-rehype.mjs +0 -21
- package/.output/server/_libs/space-separated-tokens.mjs +0 -6
- package/.output/server/_libs/style-to-js.mjs +0 -72
- package/.output/server/_libs/style-to-object.mjs +0 -38
- package/.output/server/_libs/swr.mjs +0 -947
- package/.output/server/_libs/tailwind-merge.mjs +0 -3255
- package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
- package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
- package/.output/server/_libs/three.mjs +0 -26016
- package/.output/server/_libs/trim-lines.mjs +0 -41
- package/.output/server/_libs/trough.mjs +0 -85
- package/.output/server/_libs/tslib.mjs +0 -1
- package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
- package/.output/server/_libs/unified.mjs +0 -661
- package/.output/server/_libs/unist-util-is.mjs +0 -100
- package/.output/server/_libs/unist-util-position.mjs +0 -27
- package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
- package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
- package/.output/server/_libs/unist-util-visit.mjs +0 -24
- package/.output/server/_libs/use-callback-ref.mjs +0 -66
- package/.output/server/_libs/use-sidecar.mjs +0 -106
- package/.output/server/_libs/vfile-message.mjs +0 -138
- package/.output/server/_libs/vfile.mjs +0 -467
- package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
- package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
- package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
- package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
- package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
- package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
- package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
- package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
- package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
- package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
- package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
- package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
- package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
- package/.output/server/node_modules/tslib/modules/index.js +0 -70
- package/.output/server/node_modules/tslib/modules/package.json +0 -3
- package/.output/server/node_modules/tslib/package.json +0 -47
- package/.output/server/node_modules/tslib/tslib.js +0 -484
- package/.output/server/package.json +0 -9
- package/src/routes/__root.tsx +0 -170
- package/src/routes/api/-instances.ts +0 -109
- /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
- /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
- /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
- /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
- /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
- /package/src/{routes → backend/routes}/livez.ts +0 -0
|
@@ -2,12 +2,22 @@ import http from "node:http";
|
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { isIP } from "node:net";
|
|
5
|
+
import {
|
|
6
|
+
BROWSER_RUNTIME_JSON_PLACEHOLDER,
|
|
7
|
+
BROWSER_RUNTIME_SCHEMA_VERSION,
|
|
8
|
+
type BrowserRuntimeDeployment,
|
|
9
|
+
} from "../lib/browserRuntimeContract";
|
|
10
|
+
import {
|
|
11
|
+
AGENT_INSPECTOR_BACKEND_HEADER,
|
|
12
|
+
type BackendImplementation,
|
|
13
|
+
} from "../lib/backendImplementation";
|
|
5
14
|
import {
|
|
6
15
|
AGENT_INSPECTOR_BASE_PATH_META_NAME,
|
|
7
16
|
AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER,
|
|
17
|
+
AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER,
|
|
8
18
|
AGENT_INSPECTOR_PUBLIC_PORT_HEADER,
|
|
9
19
|
getConfiguredBasePath,
|
|
10
|
-
|
|
20
|
+
normalizePublicBasePath,
|
|
11
21
|
stripBasePath,
|
|
12
22
|
stripBasePathFromRequestPath,
|
|
13
23
|
withBasePath,
|
|
@@ -18,6 +28,7 @@ import {
|
|
|
18
28
|
MANAGED_INSTANCE_TOKEN_HEADER,
|
|
19
29
|
} from "../lib/managedInstance";
|
|
20
30
|
import { lookupClientByPort } from "./socketTracker";
|
|
31
|
+
import { loadUiAsset } from "./uiAssetServer";
|
|
21
32
|
|
|
22
33
|
const IDENTITY_HEADERS = {
|
|
23
34
|
pid: "x-agent-inspector-client-pid",
|
|
@@ -32,6 +43,8 @@ const CSRF_HEADER = "x-agent-inspector-csrf";
|
|
|
32
43
|
const DEFAULT_MAX_REQUEST_BYTES = 64 * 1024 * 1024;
|
|
33
44
|
const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
34
45
|
const ALIAS_CONTROL_MAX_REQUEST_BYTES = 8 * 1024;
|
|
46
|
+
const INSTANCE_API_MAX_REQUEST_BYTES = 64 * 1024;
|
|
47
|
+
const LOCAL_CONTROL_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
35
48
|
|
|
36
49
|
export const IDENTITY_PROXY_CONTROL_NAMESPACE = "/.agent-inspector";
|
|
37
50
|
export const IDENTITY_PROXY_ALIAS_CONTROL_PATH = `${IDENTITY_PROXY_CONTROL_NAMESPACE}/base-path-aliases`;
|
|
@@ -61,26 +74,19 @@ export type BasePathAliasRegistry = {
|
|
|
61
74
|
function normalizedAliasCandidate(
|
|
62
75
|
value: string,
|
|
63
76
|
): { ok: true; basePath: string } | { ok: false; code: "invalid" | "reserved"; error: string } {
|
|
64
|
-
const normalized =
|
|
65
|
-
if (normalized
|
|
66
|
-
return {
|
|
67
|
-
ok: false,
|
|
68
|
-
code: "invalid",
|
|
69
|
-
error: "Base Path aliases must be non-root paths such as /inspector.",
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
if (!/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/u.test(normalized)) {
|
|
77
|
+
const normalized = normalizePublicBasePath(value);
|
|
78
|
+
if (normalized === null) {
|
|
73
79
|
return {
|
|
74
80
|
ok: false,
|
|
75
81
|
code: "invalid",
|
|
76
82
|
error: "Base Path aliases may contain only URL-safe path segments.",
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
|
-
if (normalized.
|
|
85
|
+
if (normalized.length === 0) {
|
|
80
86
|
return {
|
|
81
87
|
ok: false,
|
|
82
88
|
code: "invalid",
|
|
83
|
-
error: "Base Path aliases
|
|
89
|
+
error: "Base Path aliases must be non-root paths such as /inspector.",
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
if (
|
|
@@ -122,7 +128,7 @@ export function createBasePathAliasRegistry(
|
|
|
122
128
|
canonicalBasePath: string,
|
|
123
129
|
initialAliases: readonly string[] = [],
|
|
124
130
|
): BasePathAliasRegistry {
|
|
125
|
-
const canonical =
|
|
131
|
+
const canonical = normalizePublicBasePath(canonicalBasePath) ?? "";
|
|
126
132
|
const aliases: string[] = [];
|
|
127
133
|
|
|
128
134
|
const add = (value: string): BasePathAliasMutationResult => {
|
|
@@ -278,8 +284,9 @@ function rewriteRootRelativePublicPath(
|
|
|
278
284
|
sourceBasePath: string,
|
|
279
285
|
): string {
|
|
280
286
|
if (!value.startsWith("/") || value.startsWith("//")) return value;
|
|
281
|
-
const normalizedEffective =
|
|
282
|
-
const normalizedSource =
|
|
287
|
+
const normalizedEffective = normalizePublicBasePath(effectiveBasePath);
|
|
288
|
+
const normalizedSource = normalizePublicBasePath(sourceBasePath);
|
|
289
|
+
if (normalizedEffective === null || normalizedSource === null) return value;
|
|
283
290
|
const suffixIndex = value.search(/[?#]/u);
|
|
284
291
|
const pathname = suffixIndex === -1 ? value : value.slice(0, suffixIndex);
|
|
285
292
|
const suffix = suffixIndex === -1 ? "" : value.slice(suffixIndex);
|
|
@@ -299,11 +306,15 @@ function rewriteQuotedAssetPaths(
|
|
|
299
306
|
source: string,
|
|
300
307
|
effectiveBasePath: string,
|
|
301
308
|
sourceBasePath: string,
|
|
309
|
+
escape: (value: string, quote: string) => string,
|
|
302
310
|
): string {
|
|
303
311
|
return source.replace(
|
|
304
312
|
/(["'])(\/(?:[^"'/]+\/)*assets\/)/gu,
|
|
305
313
|
(_match: string, quote: string, assetPrefix: string) =>
|
|
306
|
-
`${quote}${
|
|
314
|
+
`${quote}${escape(
|
|
315
|
+
rewriteRootRelativePublicPath(assetPrefix, effectiveBasePath, sourceBasePath),
|
|
316
|
+
quote,
|
|
317
|
+
)}`,
|
|
307
318
|
);
|
|
308
319
|
}
|
|
309
320
|
|
|
@@ -312,7 +323,9 @@ export function rewriteHtmlForBasePath(
|
|
|
312
323
|
basePath: string,
|
|
313
324
|
sourceBasePath = "",
|
|
314
325
|
): string {
|
|
315
|
-
const normalized =
|
|
326
|
+
const normalized = normalizePublicBasePath(basePath);
|
|
327
|
+
const normalizedSource = normalizePublicBasePath(sourceBasePath);
|
|
328
|
+
if (normalized === null || normalizedSource === null) return html;
|
|
316
329
|
const escaped = htmlAttributeEscape(normalized);
|
|
317
330
|
const meta = `<meta name="${AGENT_INSPECTOR_BASE_PATH_META_NAME}" content="${escaped}">`;
|
|
318
331
|
const metaPattern = new RegExp(
|
|
@@ -331,13 +344,26 @@ export function rewriteHtmlForBasePath(
|
|
|
331
344
|
const withRewrittenAttributes = withMeta.replace(
|
|
332
345
|
/\b(href|src|action)=(["'])(\/(?!\/)[^"']*)\2/giu,
|
|
333
346
|
(_match: string, attribute: string, quote: string, path: string) =>
|
|
334
|
-
`${attribute}=${quote}${
|
|
347
|
+
`${attribute}=${quote}${htmlAttributeEscape(
|
|
348
|
+
rewriteRootRelativePublicPath(path, normalized, normalizedSource),
|
|
349
|
+
)}${quote}`,
|
|
350
|
+
);
|
|
351
|
+
return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, normalizedSource, (value) =>
|
|
352
|
+
htmlAttributeEscape(value),
|
|
335
353
|
);
|
|
336
|
-
return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, sourceBasePath);
|
|
337
354
|
}
|
|
338
355
|
|
|
339
|
-
function javaScriptStringEscape(value: string): string {
|
|
340
|
-
|
|
356
|
+
function javaScriptStringEscape(value: string, quote: string): string {
|
|
357
|
+
const escaped = value
|
|
358
|
+
.replace(/\\/gu, "\\\\")
|
|
359
|
+
.replace(/\r/gu, "\\r")
|
|
360
|
+
.replace(/\n/gu, "\\n")
|
|
361
|
+
.replace(/\u2028/gu, "\\u2028")
|
|
362
|
+
.replace(/\u2029/gu, "\\u2029")
|
|
363
|
+
.replace(/&/gu, "\\u0026")
|
|
364
|
+
.replace(/</gu, "\\u003c")
|
|
365
|
+
.replace(/>/gu, "\\u003e");
|
|
366
|
+
return quote === "'" ? escaped.replace(/'/gu, "\\'") : escaped.replace(/"/gu, '\\"');
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
export function rewriteJavaScriptForBasePath(
|
|
@@ -345,25 +371,123 @@ export function rewriteJavaScriptForBasePath(
|
|
|
345
371
|
basePath: string,
|
|
346
372
|
sourceBasePath = "",
|
|
347
373
|
): string {
|
|
348
|
-
const normalized =
|
|
349
|
-
const
|
|
374
|
+
const normalized = normalizePublicBasePath(basePath);
|
|
375
|
+
const normalizedSource = normalizePublicBasePath(sourceBasePath);
|
|
376
|
+
if (normalized === null || normalizedSource === null) return source;
|
|
377
|
+
const escaped = javaScriptStringEscape(normalized, '"');
|
|
350
378
|
const withRouterBasePath = source.replace(
|
|
351
379
|
/\bbasepath:\s*(["'])[^"']*\1/gu,
|
|
352
380
|
`basepath:"${escaped}"`,
|
|
353
381
|
);
|
|
354
382
|
const withDynamicPaths = withRouterBasePath.replace(
|
|
355
383
|
/return(["'])(\/(?!\/)[^"']*)\1\+([A-Za-z_$][\w$]*)/gu,
|
|
356
|
-
(_match: string, quote: string, path: string, variable: string) =>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
(_match: string, quote: string, path: string, variable: string) => {
|
|
385
|
+
const rewritten = rewriteRootRelativePublicPath(path, normalized, normalizedSource);
|
|
386
|
+
return `return${quote}${javaScriptStringEscape(rewritten, quote)}${quote}+${variable}`;
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
return rewriteQuotedAssetPaths(
|
|
390
|
+
withDynamicPaths,
|
|
391
|
+
normalized,
|
|
392
|
+
normalizedSource,
|
|
393
|
+
javaScriptStringEscape,
|
|
362
394
|
);
|
|
363
|
-
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function publicOriginForRequest(req: http.IncomingMessage, options: IdentityProxyOptions): string {
|
|
398
|
+
if (options.publicOrigin !== undefined) {
|
|
399
|
+
try {
|
|
400
|
+
return new URL(options.publicOrigin).origin;
|
|
401
|
+
} catch {
|
|
402
|
+
// Invalid explicit values are rejected by the CLI; retain a safe local fallback for embedders.
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return `http://${req.headers.host ?? "localhost"}`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function browserRuntimeForRequest(
|
|
409
|
+
req: http.IncomingMessage,
|
|
410
|
+
options: IdentityProxyOptions,
|
|
411
|
+
basePath: string,
|
|
412
|
+
): BrowserRuntimeDeployment {
|
|
413
|
+
const origin = publicOriginForRequest(req, options);
|
|
414
|
+
const normalized = normalizePublicBasePath(basePath) ?? "";
|
|
415
|
+
const baseUrl = normalized.length === 0 ? origin : `${origin}${normalized}`;
|
|
416
|
+
return {
|
|
417
|
+
schemaVersion: BROWSER_RUNTIME_SCHEMA_VERSION,
|
|
418
|
+
deploymentMode: "composed",
|
|
419
|
+
ui: { basePath: normalized.length === 0 ? "/" : normalized },
|
|
420
|
+
control: { baseUrl },
|
|
421
|
+
agent: { baseUrl },
|
|
422
|
+
capabilities: {
|
|
423
|
+
rest: true,
|
|
424
|
+
sse: true,
|
|
425
|
+
proxy: true,
|
|
426
|
+
mcp: true,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function safeInlineJson(value: unknown): string {
|
|
432
|
+
return JSON.stringify(value)
|
|
433
|
+
.replace(/&/gu, "\\u0026")
|
|
434
|
+
.replace(/</gu, "\\u003c")
|
|
435
|
+
.replace(/>/gu, "\\u003e")
|
|
436
|
+
.replace(/\u2028/gu, "\\u2028")
|
|
437
|
+
.replace(/\u2029/gu, "\\u2029");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function injectBrowserRuntime(html: string, runtime: BrowserRuntimeDeployment): string {
|
|
441
|
+
const runtimeJson = safeInlineJson(runtime);
|
|
442
|
+
if (html.includes(BROWSER_RUNTIME_JSON_PLACEHOLDER)) {
|
|
443
|
+
return html.replaceAll(BROWSER_RUNTIME_JSON_PLACEHOLDER, runtimeJson);
|
|
444
|
+
}
|
|
445
|
+
const bootstrap = `<script>window.__AGENT_INSPECTOR_RUNTIME__=${runtimeJson};</script>`;
|
|
446
|
+
return html.replace(/<head(\s[^>]*)?>/iu, (head) => `${head}${bootstrap}`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function tryServeUiAsset(
|
|
450
|
+
req: http.IncomingMessage,
|
|
451
|
+
res: http.ServerResponse,
|
|
452
|
+
options: IdentityProxyOptions,
|
|
453
|
+
pathname: string,
|
|
454
|
+
basePath: string,
|
|
455
|
+
sourceBasePath: string,
|
|
456
|
+
): Promise<boolean> {
|
|
457
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
458
|
+
if ((method !== "GET" && method !== "HEAD") || options.uiDirectory === undefined) return false;
|
|
459
|
+
const asset = await loadUiAsset(options.uiDirectory, pathname);
|
|
460
|
+
if (asset === null) return false;
|
|
461
|
+
let body = asset.body;
|
|
462
|
+
if (asset.document) {
|
|
463
|
+
const rewritten = rewriteHtmlForBasePath(body.toString("utf8"), basePath, sourceBasePath);
|
|
464
|
+
body = Buffer.from(
|
|
465
|
+
injectBrowserRuntime(rewritten, browserRuntimeForRequest(req, options, basePath)),
|
|
466
|
+
"utf8",
|
|
467
|
+
);
|
|
468
|
+
} else if (basePath.length > 0 && asset.contentType.includes("javascript")) {
|
|
469
|
+
body = Buffer.from(
|
|
470
|
+
rewriteJavaScriptForBasePath(body.toString("utf8"), basePath, sourceBasePath),
|
|
471
|
+
"utf8",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const headers: Record<string, string | number> = {
|
|
475
|
+
...BASE_SECURITY_HEADERS,
|
|
476
|
+
"cache-control": asset.cacheControl,
|
|
477
|
+
"content-length": body.byteLength,
|
|
478
|
+
"content-type": asset.contentType,
|
|
479
|
+
};
|
|
480
|
+
if (asset.document) headers["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
|
|
481
|
+
res.writeHead(200, headers);
|
|
482
|
+
if (method === "HEAD") res.end();
|
|
483
|
+
else res.end(body);
|
|
484
|
+
req.resume();
|
|
485
|
+
return true;
|
|
364
486
|
}
|
|
365
487
|
|
|
366
488
|
export type IdentityProxyOptions = {
|
|
489
|
+
/** Backend child implementation supervised behind this protected ingress. */
|
|
490
|
+
backendImplementation?: BackendImplementation;
|
|
367
491
|
/** Public port to listen on. Clients point at this. */
|
|
368
492
|
listenPort: number;
|
|
369
493
|
/** Interface exposed to clients. Defaults are chosen by the CLI. */
|
|
@@ -384,6 +508,12 @@ export type IdentityProxyOptions = {
|
|
|
384
508
|
allowUnauthenticatedRemoteProxy?: boolean;
|
|
385
509
|
/** Additional public hostnames accepted by the ingress Host boundary. */
|
|
386
510
|
trustedHosts?: readonly string[];
|
|
511
|
+
/** Exact browser UI origins allowed to call control REST/SSE across origins. */
|
|
512
|
+
allowedUiOrigins?: readonly string[];
|
|
513
|
+
/** Independent static UI artifact directory served only by the protected ingress. */
|
|
514
|
+
uiDirectory?: string;
|
|
515
|
+
/** Explicit public origin for reverse-proxy deployments; never inferred from forwarded headers. */
|
|
516
|
+
publicOrigin?: string;
|
|
387
517
|
/** Maximum declared or streamed request body size. */
|
|
388
518
|
maxRequestBytes?: number;
|
|
389
519
|
/** Deadline for receiving and forwarding one request body. */
|
|
@@ -392,9 +522,12 @@ export type IdentityProxyOptions = {
|
|
|
392
522
|
aliasRegistry?: BasePathAliasRegistry;
|
|
393
523
|
/** Private lifecycle identity exposed only to the owning local instance manager. */
|
|
394
524
|
managedInstance?: ManagedInstanceControlOptions;
|
|
525
|
+
/** Optional Node-supervisor instance API retained when the selected backend is Rust. */
|
|
526
|
+
instanceApiHandler?: (request: Request) => Promise<Response>;
|
|
395
527
|
};
|
|
396
528
|
|
|
397
529
|
export type ManagedInstanceControlOptions = {
|
|
530
|
+
backend?: BackendImplementation;
|
|
398
531
|
name: string;
|
|
399
532
|
instanceId: string;
|
|
400
533
|
launchId: string;
|
|
@@ -452,7 +585,52 @@ function normalizedHostname(value: string): string | null {
|
|
|
452
585
|
}
|
|
453
586
|
|
|
454
587
|
function isLoopbackHostname(hostname: string): boolean {
|
|
455
|
-
|
|
588
|
+
if (hostname === "localhost" || hostname === "::1") return true;
|
|
589
|
+
if (isIP(hostname) !== 4) return false;
|
|
590
|
+
return hostname.split(".", 1)[0] === "127";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export type UiOriginAllowlist = {
|
|
594
|
+
origins: readonly string[];
|
|
595
|
+
rejected: readonly string[];
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
function parseUrl(value: string): URL | null {
|
|
599
|
+
try {
|
|
600
|
+
return new URL(value);
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function parseUiOriginAllowlist(value: string | undefined): UiOriginAllowlist {
|
|
607
|
+
const origins: string[] = [];
|
|
608
|
+
const rejected: string[] = [];
|
|
609
|
+
for (const entry of (value ?? "").split(",")) {
|
|
610
|
+
const candidate = entry.trim();
|
|
611
|
+
if (candidate.length === 0) continue;
|
|
612
|
+
const parsed = parseUrl(candidate);
|
|
613
|
+
const valid =
|
|
614
|
+
parsed !== null &&
|
|
615
|
+
(parsed.protocol === "http:" || parsed.protocol === "https:") &&
|
|
616
|
+
parsed.username.length === 0 &&
|
|
617
|
+
parsed.password.length === 0 &&
|
|
618
|
+
parsed.pathname === "/" &&
|
|
619
|
+
parsed.search.length === 0 &&
|
|
620
|
+
parsed.hash.length === 0 &&
|
|
621
|
+
isLoopbackHostname(parsed.hostname.toLowerCase().replace(/^\[|\]$/gu, ""));
|
|
622
|
+
if (!valid || parsed === null) {
|
|
623
|
+
rejected.push(candidate);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (!origins.includes(parsed.origin)) origins.push(parsed.origin);
|
|
627
|
+
}
|
|
628
|
+
return { origins, rejected };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function normalizeTrustedPublicOrigin(value: string | undefined): string | null | undefined {
|
|
632
|
+
if (value === undefined) return undefined;
|
|
633
|
+
return normalizedRequestOrigin(value.trim());
|
|
456
634
|
}
|
|
457
635
|
|
|
458
636
|
export function isTrustedIdentityProxyHost(
|
|
@@ -476,6 +654,111 @@ function isSafeMethod(method: string | undefined): boolean {
|
|
|
476
654
|
return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
|
|
477
655
|
}
|
|
478
656
|
|
|
657
|
+
const UI_CORS_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
|
|
658
|
+
const UI_CORS_HEADERS = ["authorization", "content-type", "last-event-id", CSRF_HEADER] as const;
|
|
659
|
+
|
|
660
|
+
function normalizedRequestOrigin(origin: string): string | null {
|
|
661
|
+
try {
|
|
662
|
+
const parsed = new URL(origin);
|
|
663
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
664
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) return null;
|
|
665
|
+
if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) return null;
|
|
666
|
+
return parsed.origin;
|
|
667
|
+
} catch {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function isSameRequestOrigin(
|
|
673
|
+
origin: string,
|
|
674
|
+
host: string | undefined,
|
|
675
|
+
publicOrigin?: string,
|
|
676
|
+
): boolean {
|
|
677
|
+
const parsedOrigin = normalizedRequestOrigin(origin);
|
|
678
|
+
if (parsedOrigin === null) return false;
|
|
679
|
+
const expected = normalizedRequestOrigin(publicOrigin ?? `http://${host ?? "localhost"}`);
|
|
680
|
+
return expected !== null && parsedOrigin === expected;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function matchedUiOrigin(
|
|
684
|
+
origin: string | undefined,
|
|
685
|
+
allowedUiOrigins: readonly string[] | undefined,
|
|
686
|
+
): string | null {
|
|
687
|
+
if (origin === undefined) return null;
|
|
688
|
+
return (allowedUiOrigins ?? []).find((candidate) => candidate === origin) ?? null;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function isUiControlPath(pathname: string): boolean {
|
|
692
|
+
if (pathname === "/livez" || pathname === "/readyz") return true;
|
|
693
|
+
if (pathname === "/api/mcp") return false;
|
|
694
|
+
return pathname === "/api" || pathname.startsWith("/api/");
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function isModelProxyPath(pathname: string): boolean {
|
|
698
|
+
return pathname === "/proxy" || pathname.startsWith("/proxy/");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function uiCorsHeaders(origin: string | null): Record<string, string> {
|
|
702
|
+
if (origin === null) return {};
|
|
703
|
+
return {
|
|
704
|
+
"access-control-allow-origin": origin,
|
|
705
|
+
"access-control-expose-headers": "Content-Disposition",
|
|
706
|
+
vary: "Origin",
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function appendVaryHeader(existing: string | string[] | undefined, name: string): string {
|
|
711
|
+
const values = (Array.isArray(existing) ? existing.join(",") : (existing ?? ""))
|
|
712
|
+
.split(",")
|
|
713
|
+
.map((value) => value.trim())
|
|
714
|
+
.filter((value) => value.length > 0);
|
|
715
|
+
if (!values.some((value) => value.toLowerCase() === name.toLowerCase())) values.push(name);
|
|
716
|
+
return values.join(", ");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function requestedCorsHeaders(value: string | undefined): readonly string[] {
|
|
720
|
+
return (value ?? "")
|
|
721
|
+
.split(",")
|
|
722
|
+
.map((header) => header.trim().toLowerCase())
|
|
723
|
+
.filter((header) => header.length > 0);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function handleUiCorsPreflight(
|
|
727
|
+
req: http.IncomingMessage,
|
|
728
|
+
res: http.ServerResponse,
|
|
729
|
+
origin: string,
|
|
730
|
+
): void {
|
|
731
|
+
const requestedMethod = firstHeader(req.headers["access-control-request-method"])?.toUpperCase();
|
|
732
|
+
const requestedHeaders = requestedCorsHeaders(
|
|
733
|
+
firstHeader(req.headers["access-control-request-headers"]),
|
|
734
|
+
);
|
|
735
|
+
const methodAllowed =
|
|
736
|
+
requestedMethod !== undefined && UI_CORS_METHODS.some((method) => method === requestedMethod);
|
|
737
|
+
const headersAllowed = requestedHeaders.every((header) =>
|
|
738
|
+
UI_CORS_HEADERS.some((allowedHeader) => allowedHeader === header),
|
|
739
|
+
);
|
|
740
|
+
if (!methodAllowed || !headersAllowed) {
|
|
741
|
+
res.writeHead(403, {
|
|
742
|
+
"content-type": "application/json; charset=utf-8",
|
|
743
|
+
"cache-control": "no-store",
|
|
744
|
+
...uiCorsHeaders(origin),
|
|
745
|
+
});
|
|
746
|
+
res.end(JSON.stringify({ error: "CORS preflight is not allowed for this request." }));
|
|
747
|
+
req.resume();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
res.writeHead(204, {
|
|
751
|
+
"cache-control": "no-store",
|
|
752
|
+
"access-control-allow-origin": origin,
|
|
753
|
+
"access-control-allow-methods": UI_CORS_METHODS.join(", "),
|
|
754
|
+
"access-control-allow-headers": UI_CORS_HEADERS.join(", "),
|
|
755
|
+
"access-control-max-age": "600",
|
|
756
|
+
vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
|
|
757
|
+
});
|
|
758
|
+
res.end();
|
|
759
|
+
req.resume();
|
|
760
|
+
}
|
|
761
|
+
|
|
479
762
|
function isPublicHealthPath(pathname: string): boolean {
|
|
480
763
|
return pathname === "/livez" || pathname === "/readyz" || pathname === "/api/health";
|
|
481
764
|
}
|
|
@@ -503,23 +786,35 @@ export function isBrowserMutationAllowed(input: {
|
|
|
503
786
|
origin?: string;
|
|
504
787
|
secFetchSite?: string;
|
|
505
788
|
csrfToken?: string;
|
|
789
|
+
allowedUiOrigins?: readonly string[];
|
|
790
|
+
publicOrigin?: string;
|
|
506
791
|
}): boolean {
|
|
507
792
|
if (isSafeMethod(input.method)) return true;
|
|
508
793
|
const pathname = stripBasePath(
|
|
509
794
|
new URL(input.path, "http://localhost").pathname,
|
|
510
795
|
input.basePath ?? getConfiguredBasePath(process.env),
|
|
511
796
|
);
|
|
512
|
-
if (pathname === "/proxy" || pathname.startsWith("/proxy/")) return true;
|
|
513
|
-
|
|
514
797
|
const fetchSite = input.secFetchSite?.toLowerCase();
|
|
515
798
|
const browserRequest = input.origin !== undefined || fetchSite !== undefined;
|
|
516
799
|
if (!browserRequest) return true;
|
|
517
|
-
if (
|
|
800
|
+
if (isModelProxyPath(pathname)) {
|
|
801
|
+
if (fetchSite !== undefined && fetchSite !== "same-origin") return false;
|
|
802
|
+
if (
|
|
803
|
+
input.origin !== undefined &&
|
|
804
|
+
!isSameRequestOrigin(input.origin, input.host, input.publicOrigin)
|
|
805
|
+
) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
return input.csrfToken === "1";
|
|
809
|
+
}
|
|
810
|
+
const allowedOrigin = matchedUiOrigin(input.origin, input.allowedUiOrigins);
|
|
811
|
+
if (fetchSite !== undefined && fetchSite !== "same-origin" && allowedOrigin === null)
|
|
812
|
+
return false;
|
|
518
813
|
if (input.origin !== undefined) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
814
|
+
if (
|
|
815
|
+
!isSameRequestOrigin(input.origin, input.host, input.publicOrigin) &&
|
|
816
|
+
allowedOrigin === null
|
|
817
|
+
) {
|
|
523
818
|
return false;
|
|
524
819
|
}
|
|
525
820
|
}
|
|
@@ -616,6 +911,128 @@ function readAliasControlBody(req: http.IncomingMessage): Promise<AliasControlBo
|
|
|
616
911
|
});
|
|
617
912
|
}
|
|
618
913
|
|
|
914
|
+
type LocalControlBodyResult =
|
|
915
|
+
| { ok: true; body: Buffer }
|
|
916
|
+
| { ok: false; status: number; error: string };
|
|
917
|
+
|
|
918
|
+
function readLocalControlBody(
|
|
919
|
+
req: http.IncomingMessage,
|
|
920
|
+
maxBytes: number,
|
|
921
|
+
timeoutMs: number,
|
|
922
|
+
): Promise<LocalControlBodyResult> {
|
|
923
|
+
return new Promise((resolve) => {
|
|
924
|
+
const chunks: Buffer[] = [];
|
|
925
|
+
let receivedBytes = 0;
|
|
926
|
+
let settled = false;
|
|
927
|
+
const timer = setTimeout(() => {
|
|
928
|
+
finish({ ok: false, status: 408, error: "Instance request body timed out" });
|
|
929
|
+
req.resume();
|
|
930
|
+
}, timeoutMs);
|
|
931
|
+
const finish = (result: LocalControlBodyResult): void => {
|
|
932
|
+
if (settled) return;
|
|
933
|
+
settled = true;
|
|
934
|
+
clearTimeout(timer);
|
|
935
|
+
resolve(result);
|
|
936
|
+
};
|
|
937
|
+
req.on("data", (chunk: unknown) => {
|
|
938
|
+
if (settled) return;
|
|
939
|
+
const buffer = toBuffer(chunk);
|
|
940
|
+
receivedBytes += buffer.byteLength;
|
|
941
|
+
if (receivedBytes > maxBytes) {
|
|
942
|
+
finish({
|
|
943
|
+
ok: false,
|
|
944
|
+
status: 413,
|
|
945
|
+
error: `Instance request body exceeds ${String(maxBytes)} bytes`,
|
|
946
|
+
});
|
|
947
|
+
req.resume();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
chunks.push(buffer);
|
|
951
|
+
});
|
|
952
|
+
req.on("end", () => finish({ ok: true, body: Buffer.concat(chunks) }));
|
|
953
|
+
req.on("aborted", () =>
|
|
954
|
+
finish({ ok: false, status: 400, error: "Client aborted instance request body" }),
|
|
955
|
+
);
|
|
956
|
+
req.on("error", () =>
|
|
957
|
+
finish({ ok: false, status: 400, error: "Instance request body failed" }),
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function writeLocalControlError(
|
|
963
|
+
res: http.ServerResponse,
|
|
964
|
+
status: number,
|
|
965
|
+
error: string,
|
|
966
|
+
allowedUiOrigin: string | null,
|
|
967
|
+
): void {
|
|
968
|
+
res.writeHead(status, {
|
|
969
|
+
"content-type": "application/json; charset=utf-8",
|
|
970
|
+
"cache-control": "no-store",
|
|
971
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
972
|
+
});
|
|
973
|
+
res.end(JSON.stringify({ error }));
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async function handleLocalInstanceApi(
|
|
977
|
+
req: http.IncomingMessage,
|
|
978
|
+
res: http.ServerResponse,
|
|
979
|
+
handler: (request: Request) => Promise<Response>,
|
|
980
|
+
upstreamPath: string,
|
|
981
|
+
requestPublicOrigin: string,
|
|
982
|
+
allowedUiOrigin: string | null,
|
|
983
|
+
maxRequestBytes: number,
|
|
984
|
+
requestTimeoutMs: number,
|
|
985
|
+
): Promise<void> {
|
|
986
|
+
const body = await readLocalControlBody(
|
|
987
|
+
req,
|
|
988
|
+
Math.min(maxRequestBytes, INSTANCE_API_MAX_REQUEST_BYTES),
|
|
989
|
+
requestTimeoutMs,
|
|
990
|
+
);
|
|
991
|
+
if (!body.ok) {
|
|
992
|
+
writeLocalControlError(res, body.status, body.error, allowedUiOrigin);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
996
|
+
const headers = new Headers();
|
|
997
|
+
const contentType = firstHeader(req.headers["content-type"]);
|
|
998
|
+
if (contentType !== undefined) headers.set("content-type", contentType);
|
|
999
|
+
const request = new Request(new URL(upstreamPath, requestPublicOrigin), {
|
|
1000
|
+
method,
|
|
1001
|
+
headers,
|
|
1002
|
+
body: method === "GET" || method === "HEAD" ? undefined : body.body.toString("utf8"),
|
|
1003
|
+
});
|
|
1004
|
+
let response: Response;
|
|
1005
|
+
try {
|
|
1006
|
+
response = await handler(request);
|
|
1007
|
+
} catch {
|
|
1008
|
+
writeLocalControlError(res, 503, "Instance supervisor request failed", allowedUiOrigin);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
1012
|
+
if (responseBody.byteLength > LOCAL_CONTROL_MAX_RESPONSE_BYTES) {
|
|
1013
|
+
writeLocalControlError(
|
|
1014
|
+
res,
|
|
1015
|
+
502,
|
|
1016
|
+
"Instance supervisor response exceeded its resource limit",
|
|
1017
|
+
allowedUiOrigin,
|
|
1018
|
+
);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const responseHeaders: Record<string, string> = {
|
|
1022
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1023
|
+
"content-length": String(responseBody.byteLength),
|
|
1024
|
+
};
|
|
1025
|
+
response.headers.forEach((value, name) => {
|
|
1026
|
+
const lowerName = name.toLowerCase();
|
|
1027
|
+
if (!REQUEST_HOP_BY_HOP.has(lowerName) && lowerName !== "content-length") {
|
|
1028
|
+
responseHeaders[lowerName] = value;
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
responseHeaders["cache-control"] = "no-store";
|
|
1032
|
+
res.writeHead(response.status, responseHeaders);
|
|
1033
|
+
res.end(responseBody);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
619
1036
|
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
620
1037
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
621
1038
|
}
|
|
@@ -786,6 +1203,7 @@ function handleManagedInstanceControlRequest(
|
|
|
786
1203
|
}
|
|
787
1204
|
writeManagedInstanceControlResponse(res, 200, {
|
|
788
1205
|
schemaVersion: 1,
|
|
1206
|
+
backend: managedInstance.backend ?? "typescript",
|
|
789
1207
|
name: managedInstance.name,
|
|
790
1208
|
instanceId: managedInstance.instanceId,
|
|
791
1209
|
launchId: managedInstance.launchId,
|
|
@@ -847,6 +1265,27 @@ function isIdentityProxyControlPath(pathname: string): boolean {
|
|
|
847
1265
|
* best-effort scan inside TanStack Start.
|
|
848
1266
|
*/
|
|
849
1267
|
export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.Server> {
|
|
1268
|
+
const allowedUiOrigins = parseUiOriginAllowlist(options.allowedUiOrigins?.join(","));
|
|
1269
|
+
if (allowedUiOrigins.rejected.length > 0) {
|
|
1270
|
+
return Promise.reject(
|
|
1271
|
+
new Error(
|
|
1272
|
+
`Unsafe UI origins are not accepted: ${allowedUiOrigins.rejected.join(", ")}. Only exact loopback HTTP(S) origins are supported.`,
|
|
1273
|
+
),
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
const publicOrigin = normalizeTrustedPublicOrigin(options.publicOrigin);
|
|
1277
|
+
if (publicOrigin === null) {
|
|
1278
|
+
return Promise.reject(
|
|
1279
|
+
new Error(
|
|
1280
|
+
"publicOrigin must be an exact HTTP(S) origin without credentials, path, or query.",
|
|
1281
|
+
),
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
const effectiveOptions: IdentityProxyOptions = {
|
|
1285
|
+
...options,
|
|
1286
|
+
allowedUiOrigins: allowedUiOrigins.origins,
|
|
1287
|
+
publicOrigin,
|
|
1288
|
+
};
|
|
850
1289
|
return new Promise((resolve, reject) => {
|
|
851
1290
|
const server = http.createServer();
|
|
852
1291
|
const aliasRegistry =
|
|
@@ -893,7 +1332,7 @@ export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.
|
|
|
893
1332
|
};
|
|
894
1333
|
res.once("finish", release);
|
|
895
1334
|
res.once("close", release);
|
|
896
|
-
void handleRequest(req, res,
|
|
1335
|
+
void handleRequest(req, res, effectiveOptions, aliasRegistry).catch((err: unknown) => {
|
|
897
1336
|
const message = err instanceof Error ? err.message : String(err);
|
|
898
1337
|
if (!res.headersSent) {
|
|
899
1338
|
res.writeHead(502, { "content-type": "text/plain" });
|
|
@@ -979,6 +1418,55 @@ async function handleRequest(
|
|
|
979
1418
|
const resolution = resolveBasePathAliasRequest(aliasRegistry, req.url ?? "/");
|
|
980
1419
|
const basePath = resolution.effectiveBasePath;
|
|
981
1420
|
const upstreamPath = resolution.upstreamPath;
|
|
1421
|
+
const upstreamPathname = new URL(upstreamPath, "http://localhost").pathname;
|
|
1422
|
+
const requestOrigin = req.headers.origin;
|
|
1423
|
+
const requestPublicOrigin = publicOriginForRequest(req, options);
|
|
1424
|
+
const crossOriginRequest =
|
|
1425
|
+
requestOrigin !== undefined &&
|
|
1426
|
+
!isSameRequestOrigin(requestOrigin, req.headers.host, requestPublicOrigin);
|
|
1427
|
+
const allowedUiOrigin = crossOriginRequest
|
|
1428
|
+
? matchedUiOrigin(requestOrigin, options.allowedUiOrigins)
|
|
1429
|
+
: null;
|
|
1430
|
+
|
|
1431
|
+
if (crossOriginRequest && isModelProxyPath(upstreamPathname)) {
|
|
1432
|
+
res.writeHead(403, {
|
|
1433
|
+
"content-type": "application/json; charset=utf-8",
|
|
1434
|
+
"cache-control": "no-store",
|
|
1435
|
+
});
|
|
1436
|
+
res.end(
|
|
1437
|
+
JSON.stringify({ error: "Cross-origin browser access to the model proxy is forbidden." }),
|
|
1438
|
+
);
|
|
1439
|
+
req.resume();
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (crossOriginRequest && upstreamPathname === "/api/mcp") {
|
|
1444
|
+
res.writeHead(403, {
|
|
1445
|
+
"content-type": "application/json; charset=utf-8",
|
|
1446
|
+
"cache-control": "no-store",
|
|
1447
|
+
});
|
|
1448
|
+
res.end(JSON.stringify({ error: "Browser CORS is not enabled for the MCP endpoint." }));
|
|
1449
|
+
req.resume();
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (crossOriginRequest && isUiControlPath(upstreamPathname)) {
|
|
1454
|
+
if (allowedUiOrigin === null) {
|
|
1455
|
+
res.writeHead(403, {
|
|
1456
|
+
"content-type": "application/json; charset=utf-8",
|
|
1457
|
+
"cache-control": "no-store",
|
|
1458
|
+
});
|
|
1459
|
+
res.end(
|
|
1460
|
+
JSON.stringify({ error: "UI origin is not allowed by this Agent Inspector runtime." }),
|
|
1461
|
+
);
|
|
1462
|
+
req.resume();
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
if ((req.method ?? "GET").toUpperCase() === "OPTIONS") {
|
|
1466
|
+
handleUiCorsPreflight(req, res, allowedUiOrigin);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
982
1470
|
|
|
983
1471
|
if (basePath.length > 0 && requestUrl.pathname.startsWith("/assets/")) {
|
|
984
1472
|
res.writeHead(307, {
|
|
@@ -999,11 +1487,14 @@ async function handleRequest(
|
|
|
999
1487
|
origin: req.headers.origin,
|
|
1000
1488
|
secFetchSite: firstHeader(req.headers["sec-fetch-site"]),
|
|
1001
1489
|
csrfToken: firstHeader(req.headers[CSRF_HEADER]),
|
|
1490
|
+
allowedUiOrigins: options.allowedUiOrigins,
|
|
1491
|
+
publicOrigin: requestPublicOrigin,
|
|
1002
1492
|
})
|
|
1003
1493
|
) {
|
|
1004
1494
|
res.writeHead(403, {
|
|
1005
1495
|
"content-type": "application/json; charset=utf-8",
|
|
1006
1496
|
"cache-control": "no-store",
|
|
1497
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1007
1498
|
});
|
|
1008
1499
|
res.end(JSON.stringify({ error: "Browser mutation requires same-origin CSRF proof" }));
|
|
1009
1500
|
req.resume();
|
|
@@ -1024,6 +1515,7 @@ async function handleRequest(
|
|
|
1024
1515
|
res.writeHead(403, {
|
|
1025
1516
|
"content-type": "application/json; charset=utf-8",
|
|
1026
1517
|
"cache-control": "no-store",
|
|
1518
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1027
1519
|
});
|
|
1028
1520
|
res.end(
|
|
1029
1521
|
JSON.stringify({
|
|
@@ -1034,6 +1526,19 @@ async function handleRequest(
|
|
|
1034
1526
|
return;
|
|
1035
1527
|
}
|
|
1036
1528
|
|
|
1529
|
+
if (
|
|
1530
|
+
await tryServeUiAsset(
|
|
1531
|
+
req,
|
|
1532
|
+
res,
|
|
1533
|
+
options,
|
|
1534
|
+
upstreamPathname,
|
|
1535
|
+
basePath,
|
|
1536
|
+
aliasRegistry.canonicalBasePath,
|
|
1537
|
+
)
|
|
1538
|
+
) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1037
1542
|
// The connection-time lookup already populated the per-port cache; this
|
|
1038
1543
|
// call now hits the cache and returns the result synchronously.
|
|
1039
1544
|
const remotePort = req.socket.remotePort;
|
|
@@ -1057,6 +1562,7 @@ async function handleRequest(
|
|
|
1057
1562
|
res.writeHead(400, {
|
|
1058
1563
|
"content-type": "application/json; charset=utf-8",
|
|
1059
1564
|
"cache-control": "no-store",
|
|
1565
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1060
1566
|
});
|
|
1061
1567
|
res.end(JSON.stringify({ error: "Invalid Content-Length header" }));
|
|
1062
1568
|
req.resume();
|
|
@@ -1066,6 +1572,7 @@ async function handleRequest(
|
|
|
1066
1572
|
res.writeHead(413, {
|
|
1067
1573
|
"content-type": "application/json; charset=utf-8",
|
|
1068
1574
|
"cache-control": "no-store",
|
|
1575
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1069
1576
|
});
|
|
1070
1577
|
res.end(JSON.stringify({ error: `Request body exceeds ${String(maxRequestBytes)} bytes` }));
|
|
1071
1578
|
req.resume();
|
|
@@ -1082,10 +1589,12 @@ async function handleRequest(
|
|
|
1082
1589
|
)
|
|
1083
1590
|
continue;
|
|
1084
1591
|
if (lowerName.startsWith(IDENTITY_HEADER_PREFIX)) continue;
|
|
1592
|
+
if (lowerName === "authorization" && !isModelProxyPath(upstreamPathname)) continue;
|
|
1085
1593
|
if (value === undefined) continue;
|
|
1086
1594
|
upstreamHeaders[name] = value;
|
|
1087
1595
|
}
|
|
1088
1596
|
upstreamHeaders[AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER] = basePath.length === 0 ? "/" : basePath;
|
|
1597
|
+
upstreamHeaders[AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER] = requestPublicOrigin;
|
|
1089
1598
|
const publicPort = req.socket.localPort;
|
|
1090
1599
|
if (publicPort !== undefined && publicPort > 0 && publicPort <= 65_535) {
|
|
1091
1600
|
upstreamHeaders[AGENT_INSPECTOR_PUBLIC_PORT_HEADER] = String(publicPort);
|
|
@@ -1108,6 +1617,19 @@ async function handleRequest(
|
|
|
1108
1617
|
options.requestTimeoutMs,
|
|
1109
1618
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1110
1619
|
);
|
|
1620
|
+
if (options.instanceApiHandler !== undefined && isInstanceManagementPath(upstreamPathname)) {
|
|
1621
|
+
await handleLocalInstanceApi(
|
|
1622
|
+
req,
|
|
1623
|
+
res,
|
|
1624
|
+
options.instanceApiHandler,
|
|
1625
|
+
upstreamPath,
|
|
1626
|
+
requestPublicOrigin,
|
|
1627
|
+
allowedUiOrigin,
|
|
1628
|
+
maxRequestBytes,
|
|
1629
|
+
requestTimeoutMs,
|
|
1630
|
+
);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1111
1633
|
const proxyReq = http.request(
|
|
1112
1634
|
{
|
|
1113
1635
|
hostname: options.upstreamHost,
|
|
@@ -1179,6 +1701,12 @@ async function handleRequest(
|
|
|
1179
1701
|
for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
|
|
1180
1702
|
outHeaders[name] = value;
|
|
1181
1703
|
}
|
|
1704
|
+
outHeaders[AGENT_INSPECTOR_BACKEND_HEADER] = options.backendImplementation ?? "typescript";
|
|
1705
|
+
if (allowedUiOrigin !== null && isUiControlPath(upstreamPathname)) {
|
|
1706
|
+
outHeaders["access-control-allow-origin"] = allowedUiOrigin;
|
|
1707
|
+
outHeaders["access-control-expose-headers"] = "Content-Disposition";
|
|
1708
|
+
outHeaders["vary"] = appendVaryHeader(outHeaders["vary"], "Origin");
|
|
1709
|
+
}
|
|
1182
1710
|
if (contentType?.toLowerCase().includes("text/html") === true) {
|
|
1183
1711
|
outHeaders["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
|
|
1184
1712
|
}
|