@tonyclaw/agent-inspector 3.0.45 → 3.0.46
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 +2326 -516
- 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-D18yUq36.mjs} +19770 -19447
- 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-DAUuIJ6G.js} +1 -1
- package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
- package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
- package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
- package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
- package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
- package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
- package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
- package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
- package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
- package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
- package/.output/ui/assets/index-BvXp42al.css +1 -0
- package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
- package/.output/ui/assets/index-DHpwr08Z.js +73 -0
- package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
- package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
- package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.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 +175 -43
- package/docs/instance-control.openapi.yaml +59 -4
- package/package.json +25 -5
- 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/{routes → backend/routes}/api/-instances.ts +3 -3
- 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 +26 -13
- package/src/cli/instanceArgs.ts +30 -11
- package/src/cli/instanceControl.ts +0 -2
- package/src/cli/instanceModel.ts +9 -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/templates/codex-skill-onboard.ts +44 -14
- package/src/cli/templates/skill-onboard.ts +64 -28
- package/src/cli.ts +184 -61
- 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/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 +27 -11
- 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 +1 -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 +419 -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 +78 -14
- package/src/proxy/sqliteLogIndex.ts +65 -2
- 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/.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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
2
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import type {
|
|
5
5
|
EcosystemExecutionPlan,
|
|
@@ -8,15 +8,31 @@ import type {
|
|
|
8
8
|
EcosystemTaskStatus,
|
|
9
9
|
} from "../lib/ecosystemContract";
|
|
10
10
|
import { findEcosystemPackage, findEcosystemRecipe } from "./ecosystemRegistry";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
npmCommand,
|
|
13
|
+
npmSpawnCommand,
|
|
14
|
+
signalProcessTree,
|
|
15
|
+
type CommandSpec,
|
|
16
|
+
} from "./platformCommands";
|
|
12
17
|
|
|
13
18
|
const MAX_TASKS = 50;
|
|
14
19
|
const OUTPUT_LIMIT = 40_000;
|
|
15
20
|
const INSTALL_TIMEOUT_MS = 120_000;
|
|
16
21
|
const HELP_TIMEOUT_MS = 30_000;
|
|
17
22
|
const RECIPE_TIMEOUT_MS = 90_000;
|
|
23
|
+
const SHUTDOWN_GRACE_MS = 2_000;
|
|
18
24
|
|
|
19
25
|
const tasks = new Map<string, EcosystemTask>();
|
|
26
|
+
type ActiveTask = {
|
|
27
|
+
child: ChildProcess;
|
|
28
|
+
closed: Promise<void>;
|
|
29
|
+
resolveClosed: () => void;
|
|
30
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const activeTasks = new Map<string, ActiveTask>();
|
|
34
|
+
let acceptingTasks = true;
|
|
35
|
+
let shutdownForceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
20
36
|
|
|
21
37
|
function nowIso(): string {
|
|
22
38
|
return new Date().toISOString();
|
|
@@ -152,6 +168,77 @@ function completeTask(
|
|
|
152
168
|
task.finishedAt = nowIso();
|
|
153
169
|
}
|
|
154
170
|
|
|
171
|
+
function finishActiveTask(taskId: string): void {
|
|
172
|
+
const active = activeTasks.get(taskId);
|
|
173
|
+
if (active === undefined) return;
|
|
174
|
+
clearTimeout(active.timeout);
|
|
175
|
+
activeTasks.delete(taskId);
|
|
176
|
+
active.resolveClosed();
|
|
177
|
+
if (activeTasks.size === 0 && shutdownForceTimer !== null) {
|
|
178
|
+
clearTimeout(shutdownForceTimer);
|
|
179
|
+
shutdownForceTimer = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function failTaskForShutdown(task: EcosystemTask): void {
|
|
184
|
+
if (task.status !== "running" && task.status !== "queued") return;
|
|
185
|
+
const error = "Runtime shutdown terminated the ecosystem command";
|
|
186
|
+
appendTaskOutput(task, `\n${error}.\n`);
|
|
187
|
+
completeTask(task, "failed", null, error);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function forceActiveTaskTrees(): void {
|
|
191
|
+
for (const active of activeTasks.values()) {
|
|
192
|
+
const pid = active.child.pid;
|
|
193
|
+
if (pid !== undefined) signalProcessTree(pid, "SIGKILL");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function launchTask(task: EcosystemTask, spawnCommand: CommandSpec, timeoutMs: number): void {
|
|
198
|
+
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
199
|
+
detached: process.platform !== "win32",
|
|
200
|
+
windowsHide: true,
|
|
201
|
+
shell: false,
|
|
202
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
203
|
+
});
|
|
204
|
+
let resolveClosed = (): void => {};
|
|
205
|
+
const closed = new Promise<void>((resolve) => {
|
|
206
|
+
resolveClosed = resolve;
|
|
207
|
+
});
|
|
208
|
+
const timeout = setTimeout(() => {
|
|
209
|
+
const pid = child.pid;
|
|
210
|
+
if (pid !== undefined) signalProcessTree(pid, "SIGKILL");
|
|
211
|
+
appendTaskOutput(task, `\nTimed out after ${String(timeoutMs / 1000)}s.\n`);
|
|
212
|
+
}, timeoutMs);
|
|
213
|
+
activeTasks.set(task.id, { child, closed, resolveClosed, timeout });
|
|
214
|
+
|
|
215
|
+
task.status = "running";
|
|
216
|
+
task.startedAt = nowIso();
|
|
217
|
+
appendTaskOutput(task, `$ ${task.command}\n`);
|
|
218
|
+
|
|
219
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
220
|
+
appendTaskOutput(task, chunk.toString("utf8"));
|
|
221
|
+
});
|
|
222
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
223
|
+
appendTaskOutput(task, chunk.toString("utf8"));
|
|
224
|
+
});
|
|
225
|
+
child.on("error", (error) => {
|
|
226
|
+
appendTaskOutput(task, `\n${error.message}\n`);
|
|
227
|
+
if (task.status !== "failed") completeTask(task, "failed", null, error.message);
|
|
228
|
+
finishActiveTask(task.id);
|
|
229
|
+
});
|
|
230
|
+
child.on("close", (code) => {
|
|
231
|
+
if (task.status !== "failed") {
|
|
232
|
+
if (code === 0) {
|
|
233
|
+
completeTask(task, "succeeded", code, null);
|
|
234
|
+
} else {
|
|
235
|
+
completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
finishActiveTask(task.id);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
155
242
|
export function getEcosystemTask(taskId: string): EcosystemTask | null {
|
|
156
243
|
return tasks.get(taskId) ?? null;
|
|
157
244
|
}
|
|
@@ -164,6 +251,7 @@ export function startEcosystemTask(
|
|
|
164
251
|
packageId: string,
|
|
165
252
|
action: EcosystemTaskAction,
|
|
166
253
|
): EcosystemTask | null {
|
|
254
|
+
if (!acceptingTasks) return null;
|
|
167
255
|
if (action === "recipe") return null;
|
|
168
256
|
const definition =
|
|
169
257
|
action === "runner-presets"
|
|
@@ -190,46 +278,13 @@ export function startEcosystemTask(
|
|
|
190
278
|
};
|
|
191
279
|
rememberTask(task);
|
|
192
280
|
|
|
193
|
-
|
|
194
|
-
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
195
|
-
windowsHide: true,
|
|
196
|
-
shell: false,
|
|
197
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
198
|
-
});
|
|
199
|
-
const timeout = setTimeout(() => {
|
|
200
|
-
child.kill();
|
|
201
|
-
appendTaskOutput(task, `\nTimed out after ${String(packageActionTimeout(action) / 1000)}s.\n`);
|
|
202
|
-
}, packageActionTimeout(action));
|
|
203
|
-
|
|
204
|
-
task.status = "running";
|
|
205
|
-
task.startedAt = nowIso();
|
|
206
|
-
appendTaskOutput(task, `$ ${task.command}\n`);
|
|
207
|
-
|
|
208
|
-
child.stdout.on("data", (chunk: Buffer) => {
|
|
209
|
-
appendTaskOutput(task, chunk.toString("utf8"));
|
|
210
|
-
});
|
|
211
|
-
child.stderr.on("data", (chunk: Buffer) => {
|
|
212
|
-
appendTaskOutput(task, chunk.toString("utf8"));
|
|
213
|
-
});
|
|
214
|
-
child.on("error", (error) => {
|
|
215
|
-
clearTimeout(timeout);
|
|
216
|
-
appendTaskOutput(task, `\n${error.message}\n`);
|
|
217
|
-
completeTask(task, "failed", null, error.message);
|
|
218
|
-
});
|
|
219
|
-
child.on("close", (code) => {
|
|
220
|
-
clearTimeout(timeout);
|
|
221
|
-
if (task.status === "failed") return;
|
|
222
|
-
if (code === 0) {
|
|
223
|
-
completeTask(task, "succeeded", code, null);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
|
|
227
|
-
});
|
|
281
|
+
launchTask(task, npmSpawnCommand(args), packageActionTimeout(action));
|
|
228
282
|
|
|
229
283
|
return task;
|
|
230
284
|
}
|
|
231
285
|
|
|
232
286
|
export function startEcosystemRecipeTask(recipeId: string): EcosystemTask | null {
|
|
287
|
+
if (!acceptingTasks) return null;
|
|
233
288
|
const recipe = findEcosystemRecipe(recipeId);
|
|
234
289
|
if (recipe === null) return null;
|
|
235
290
|
if (!recipe.runnable) return null;
|
|
@@ -255,41 +310,77 @@ export function startEcosystemRecipeTask(recipeId: string): EcosystemTask | null
|
|
|
255
310
|
};
|
|
256
311
|
rememberTask(task);
|
|
257
312
|
|
|
258
|
-
|
|
259
|
-
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
260
|
-
windowsHide: true,
|
|
261
|
-
shell: false,
|
|
262
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
263
|
-
});
|
|
264
|
-
const timeout = setTimeout(() => {
|
|
265
|
-
child.kill();
|
|
266
|
-
appendTaskOutput(task, `\nTimed out after ${String(RECIPE_TIMEOUT_MS / 1000)}s.\n`);
|
|
267
|
-
}, RECIPE_TIMEOUT_MS);
|
|
313
|
+
launchTask(task, npmSpawnCommand(args), RECIPE_TIMEOUT_MS);
|
|
268
314
|
|
|
269
|
-
task
|
|
270
|
-
|
|
271
|
-
appendTaskOutput(task, `$ ${task.command}\n`);
|
|
315
|
+
return task;
|
|
316
|
+
}
|
|
272
317
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
318
|
+
/** Stop admitting commands and begin terminating every tracked child process tree. */
|
|
319
|
+
export function beginEcosystemTaskShutdown(): void {
|
|
320
|
+
acceptingTasks = false;
|
|
321
|
+
for (const [taskId, active] of activeTasks) {
|
|
322
|
+
const task = tasks.get(taskId);
|
|
323
|
+
if (task !== undefined) failTaskForShutdown(task);
|
|
324
|
+
clearTimeout(active.timeout);
|
|
325
|
+
const pid = active.child.pid;
|
|
326
|
+
if (pid !== undefined) signalProcessTree(pid, "SIGTERM");
|
|
327
|
+
}
|
|
328
|
+
if (activeTasks.size > 0 && shutdownForceTimer === null) {
|
|
329
|
+
shutdownForceTimer = setTimeout(() => {
|
|
330
|
+
shutdownForceTimer = null;
|
|
331
|
+
forceActiveTaskTrees();
|
|
332
|
+
}, SHUTDOWN_GRACE_MS);
|
|
333
|
+
shutdownForceTimer.unref();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Wait until children signalled by beginEcosystemTaskShutdown have closed. */
|
|
338
|
+
export async function waitForEcosystemTasksToClose(): Promise<void> {
|
|
339
|
+
await Promise.all([...activeTasks.values()].map(async (active) => await active.closed));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Immediate deadline fallback used by the runtime shutdown coordinator. */
|
|
343
|
+
export function forceCloseEcosystemTasks(): void {
|
|
344
|
+
acceptingTasks = false;
|
|
345
|
+
if (shutdownForceTimer !== null) {
|
|
346
|
+
clearTimeout(shutdownForceTimer);
|
|
347
|
+
shutdownForceTimer = null;
|
|
348
|
+
}
|
|
349
|
+
for (const taskId of activeTasks.keys()) {
|
|
350
|
+
const task = tasks.get(taskId);
|
|
351
|
+
if (task !== undefined) failTaskForShutdown(task);
|
|
352
|
+
}
|
|
353
|
+
forceActiveTaskTrees();
|
|
354
|
+
}
|
|
293
355
|
|
|
356
|
+
export function _startEcosystemTaskForTests(
|
|
357
|
+
spawnCommand: CommandSpec,
|
|
358
|
+
timeoutMs = RECIPE_TIMEOUT_MS,
|
|
359
|
+
): EcosystemTask | null {
|
|
360
|
+
if (!acceptingTasks) return null;
|
|
361
|
+
const task: EcosystemTask = {
|
|
362
|
+
id: randomUUID(),
|
|
363
|
+
packageId: "test-package",
|
|
364
|
+
npmName: "test-package",
|
|
365
|
+
recipeId: null,
|
|
366
|
+
action: "help",
|
|
367
|
+
status: "queued",
|
|
368
|
+
command: `${spawnCommand.command} ${spawnCommand.args.join(" ")}`,
|
|
369
|
+
output: "",
|
|
370
|
+
createdAt: nowIso(),
|
|
371
|
+
startedAt: null,
|
|
372
|
+
finishedAt: null,
|
|
373
|
+
exitCode: null,
|
|
374
|
+
error: null,
|
|
375
|
+
};
|
|
376
|
+
rememberTask(task);
|
|
377
|
+
launchTask(task, spawnCommand, timeoutMs);
|
|
294
378
|
return task;
|
|
295
379
|
}
|
|
380
|
+
|
|
381
|
+
export function _resetEcosystemTasksForTests(): void {
|
|
382
|
+
forceCloseEcosystemTasks();
|
|
383
|
+
tasks.clear();
|
|
384
|
+
activeTasks.clear();
|
|
385
|
+
acceptingTasks = true;
|
|
386
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { FormatHandler } from "./handler";
|
|
2
2
|
import type { ApiFormat } from "../schemas";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
PATH_V1_RESPONSES,
|
|
7
|
-
PATH_RESPONSES,
|
|
8
|
-
PATH_V1_MESSAGES,
|
|
9
|
-
} from "../constants";
|
|
3
|
+
import { API_FORMAT_PATH_ENTRIES, apiFormatForPath } from "../../lib/apiFormat";
|
|
4
|
+
|
|
5
|
+
export { apiFormatForPath };
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* API format registry maps request paths and API formats to their FormatHandler.
|
|
@@ -63,18 +59,7 @@ export function formatForPath(path: string): FormatHandler | null {
|
|
|
63
59
|
return formatRegistry.getByPath(path) ?? null;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
/**
|
|
67
|
-
* Resolve the API format from the request endpoint. The endpoint is
|
|
68
|
-
* authoritative because Anthropic and OpenAI request bodies overlap for
|
|
69
|
-
* simple message-only calls.
|
|
70
|
-
*/
|
|
71
|
-
export function apiFormatForPath(path: string): ApiFormat {
|
|
72
|
-
return formatRegistry.getApiFormatByPath(path) ?? "unknown";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
62
|
// Register known paths
|
|
76
|
-
|
|
77
|
-
formatRegistry.registerPath(
|
|
78
|
-
|
|
79
|
-
formatRegistry.registerPath(PATH_V1_RESPONSES, "openai");
|
|
80
|
-
formatRegistry.registerPath(PATH_RESPONSES, "openai");
|
|
63
|
+
for (const [path, format] of API_FORMAT_PATH_ENTRIES) {
|
|
64
|
+
formatRegistry.registerPath(path, format);
|
|
65
|
+
}
|
package/src/proxy/handler.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createLog, getNextLogId, persistFinalizedLogUpdate, type CapturedLog } from "./store";
|
|
2
3
|
import { appendLogEntry, logger } from "./logger";
|
|
3
4
|
import { extractRequestMetadata } from "./schemas";
|
|
4
|
-
import { registry } from "./formats";
|
|
5
5
|
import { findProviderByModel } from "./providers";
|
|
6
6
|
import { getClientInfo } from "./socketTracker";
|
|
7
7
|
import { formatForPath } from "./formats";
|
|
@@ -25,18 +25,26 @@ import { getConfig } from "./config";
|
|
|
25
25
|
import { safeFetch } from "../lib/safeFetch";
|
|
26
26
|
|
|
27
27
|
const ALLOW_LOOPBACK = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
|
|
28
|
-
|
|
28
|
+
const EMPTY_UPSTREAM_BODY_ERROR = "Upstream response did not include a body";
|
|
29
|
+
const UPSTREAM_STREAM_ERROR = "Upstream stream terminated unexpectedly";
|
|
30
|
+
import { stripClaudeCodeBillingHeader } from "../lib/claudeCodeStrip";
|
|
29
31
|
import { stripOpenAIOrphanToolMessages } from "./openaiOrphanToolStrip";
|
|
30
32
|
import { maskApiKey } from "../lib/mask";
|
|
31
33
|
import { buildFileLogEntry, type FinalizeLogJob } from "./logFinalizer";
|
|
32
34
|
import { enqueueFinalizeLogJob } from "./sessionRuntime";
|
|
33
35
|
import { RawStreamCapture } from "./rawStreamCapture";
|
|
34
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
acquireRuntimeRequest,
|
|
38
|
+
getRuntimeAdmissionState,
|
|
39
|
+
type RuntimeRequestLease,
|
|
40
|
+
} from "./runtimeAdmission";
|
|
41
|
+
import { resourceLimitResponse } from "../lib/resourceLimits";
|
|
35
42
|
import {
|
|
36
43
|
buildUpstreamUrl,
|
|
37
44
|
describeApiRoute,
|
|
38
45
|
getProxyApiPath,
|
|
39
46
|
injectProviderAuth,
|
|
47
|
+
providerSupportsApiRoute,
|
|
40
48
|
selectUpstreamBase,
|
|
41
49
|
setUpstreamHost,
|
|
42
50
|
} from "./upstream";
|
|
@@ -181,31 +189,81 @@ function handleNonStreamingResponse(
|
|
|
181
189
|
});
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
function
|
|
192
|
+
function applyRawStreamCaptureStatus(log: CapturedLog, capture: RawStreamCapture): void {
|
|
193
|
+
const status = capture.status;
|
|
194
|
+
log.captureIncomplete = status.incomplete;
|
|
195
|
+
log.droppedChunks = status.droppedChunks;
|
|
196
|
+
log.droppedBytes = status.droppedBytes;
|
|
197
|
+
log.captureIncompleteReason = status.reason;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function applyUpstreamStreamError(log: CapturedLog): void {
|
|
201
|
+
log.captureIncomplete = true;
|
|
202
|
+
log.captureIncompleteReason = "upstream-stream-error";
|
|
203
|
+
log.error = UPSTREAM_STREAM_ERROR;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function handleStreamingResponse(
|
|
185
207
|
upstreamRes: Response,
|
|
186
208
|
req: Request,
|
|
187
209
|
startTime: number,
|
|
188
210
|
upstreamUrl: string,
|
|
189
211
|
log: CapturedLog,
|
|
190
212
|
collectStreamingChunks: boolean,
|
|
191
|
-
): Response {
|
|
213
|
+
): Promise<Response> {
|
|
192
214
|
log.streaming = true;
|
|
193
215
|
log.responseStatus = upstreamRes.status;
|
|
194
216
|
|
|
217
|
+
if (upstreamRes.body === null) {
|
|
218
|
+
log.elapsedMs = Date.now() - startTime;
|
|
219
|
+
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
220
|
+
log.responseText = EMPTY_UPSTREAM_BODY_ERROR;
|
|
221
|
+
log.captureIncomplete = true;
|
|
222
|
+
log.droppedChunks = 0;
|
|
223
|
+
log.droppedBytes = 0;
|
|
224
|
+
log.captureIncompleteReason = "upstream-stream-error";
|
|
225
|
+
log.error = EMPTY_UPSTREAM_BODY_ERROR;
|
|
226
|
+
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: EMPTY_UPSTREAM_BODY_ERROR });
|
|
227
|
+
await persistFinalizedLogUpdate(log);
|
|
228
|
+
return new Response("No response body", { status: STATUS_BAD_GATEWAY });
|
|
229
|
+
}
|
|
230
|
+
|
|
195
231
|
const capture = new RawStreamCapture();
|
|
196
232
|
const decoder = new TextDecoder();
|
|
197
233
|
let finalizationScheduled = false;
|
|
198
234
|
let firstChunkMs: number | null = null;
|
|
235
|
+
let pendingDecoderBytes = 0;
|
|
236
|
+
let downstreamCancelled = false;
|
|
237
|
+
|
|
238
|
+
const submitDecodedText = (text: string, flush: boolean = false): void => {
|
|
239
|
+
const observedBytes = flush
|
|
240
|
+
? pendingDecoderBytes
|
|
241
|
+
: Math.min(pendingDecoderBytes, Buffer.byteLength(text, "utf-8"));
|
|
242
|
+
pendingDecoderBytes -= observedBytes;
|
|
243
|
+
if (text === "" && observedBytes === 0) return;
|
|
244
|
+
capture.tryAppend(text, observedBytes);
|
|
245
|
+
applyRawStreamCaptureStatus(log, capture);
|
|
246
|
+
};
|
|
199
247
|
|
|
200
|
-
const scheduleStreamingFinalization = (
|
|
248
|
+
const scheduleStreamingFinalization = (
|
|
249
|
+
type: "streaming" | "stream-abort",
|
|
250
|
+
upstreamStreamFailed: boolean = false,
|
|
251
|
+
): void => {
|
|
201
252
|
if (finalizationScheduled) return;
|
|
202
253
|
finalizationScheduled = true;
|
|
203
254
|
|
|
255
|
+
if (pendingDecoderBytes > 0) {
|
|
256
|
+
capture.recordDroppedObservation("pipeline-closed", pendingDecoderBytes);
|
|
257
|
+
pendingDecoderBytes = 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
204
260
|
const totalStreamMs = Date.now() - startTime;
|
|
205
261
|
const elapsedMs = totalStreamMs;
|
|
206
262
|
void capture
|
|
207
263
|
.finalize()
|
|
208
264
|
.then((rawStream) => {
|
|
265
|
+
applyRawStreamCaptureStatus(log, capture);
|
|
266
|
+
if (upstreamStreamFailed) applyUpstreamStreamError(log);
|
|
209
267
|
switch (type) {
|
|
210
268
|
case "streaming":
|
|
211
269
|
scheduleLogFinalization({
|
|
@@ -218,6 +276,7 @@ function handleStreamingResponse(
|
|
|
218
276
|
collectStreamingChunks,
|
|
219
277
|
firstChunkMs,
|
|
220
278
|
totalStreamMs,
|
|
279
|
+
terminalError: upstreamStreamFailed ? UPSTREAM_STREAM_ERROR : null,
|
|
221
280
|
});
|
|
222
281
|
return;
|
|
223
282
|
case "stream-abort":
|
|
@@ -237,6 +296,10 @@ function handleStreamingResponse(
|
|
|
237
296
|
})
|
|
238
297
|
.catch((error: unknown) => {
|
|
239
298
|
const message = error instanceof Error ? error.message : String(error);
|
|
299
|
+
applyRawStreamCaptureStatus(log, capture);
|
|
300
|
+
if (upstreamStreamFailed) applyUpstreamStreamError(log);
|
|
301
|
+
log.captureIncomplete = true;
|
|
302
|
+
log.captureIncompleteReason ??= "finalize-failed";
|
|
240
303
|
logger.error(
|
|
241
304
|
`[handler] Raw stream finalization failed for log #${String(log.id)}:`,
|
|
242
305
|
message,
|
|
@@ -256,28 +319,48 @@ function handleStreamingResponse(
|
|
|
256
319
|
};
|
|
257
320
|
|
|
258
321
|
const transform = new TransformStream<Uint8Array, Uint8Array>({
|
|
259
|
-
|
|
322
|
+
transform(chunk, controller) {
|
|
260
323
|
if (firstChunkMs === null) firstChunkMs = Date.now() - startTime;
|
|
261
324
|
controller.enqueue(chunk);
|
|
262
|
-
|
|
325
|
+
pendingDecoderBytes += chunk.byteLength;
|
|
326
|
+
submitDecodedText(decoder.decode(chunk, { stream: true }));
|
|
263
327
|
},
|
|
264
|
-
|
|
265
|
-
|
|
328
|
+
flush() {
|
|
329
|
+
submitDecodedText(decoder.decode(), true);
|
|
266
330
|
scheduleStreamingFinalization("streaming");
|
|
267
331
|
},
|
|
268
332
|
});
|
|
269
333
|
|
|
270
|
-
if (upstreamRes.body === null) {
|
|
271
|
-
return new Response("No response body", { status: STATUS_BAD_GATEWAY });
|
|
272
|
-
}
|
|
273
|
-
|
|
274
334
|
// pipeTo returns a promise that rejects when either side errors. Without
|
|
275
335
|
// this catch, an upstream connection drop skips flush() and leaves the log
|
|
276
336
|
// permanently un-finalized with a leaked temp file.
|
|
277
337
|
void upstreamRes.body.pipeTo(transform.writable).catch(() => {
|
|
278
|
-
|
|
338
|
+
if (downstreamCancelled || req.signal?.aborted === true) {
|
|
339
|
+
scheduleStreamingFinalization("stream-abort");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
scheduleStreamingFinalization("streaming", true);
|
|
343
|
+
});
|
|
344
|
+
const transformedReader = transform.readable.getReader();
|
|
345
|
+
const loggedStream = new ReadableStream<Uint8Array>({
|
|
346
|
+
async pull(controller) {
|
|
347
|
+
try {
|
|
348
|
+
const next = await transformedReader.read();
|
|
349
|
+
if (next.done) {
|
|
350
|
+
controller.close();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
controller.enqueue(next.value);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
controller.error(error);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
async cancel(reason) {
|
|
359
|
+
downstreamCancelled = true;
|
|
360
|
+
scheduleStreamingFinalization("stream-abort");
|
|
361
|
+
await transformedReader.cancel(reason);
|
|
362
|
+
},
|
|
279
363
|
});
|
|
280
|
-
const loggedStream = transform.readable;
|
|
281
364
|
|
|
282
365
|
if (req.signal === undefined) {
|
|
283
366
|
logger.warn("[handler] req.signal is undefined; abort detection disabled for this request");
|
|
@@ -361,6 +444,19 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
|
|
|
361
444
|
// Find provider config using already-extracted model (not calling extractModelFromBody again)
|
|
362
445
|
const matchedProviderConfig = model !== null ? findProviderByModel(model) : null;
|
|
363
446
|
const route = describeApiRoute(parsed.apiPath);
|
|
447
|
+
|
|
448
|
+
// Model matching is necessary but not sufficient: the Provider must explicitly expose the
|
|
449
|
+
// protocol selected by the request path. This guard stays before log creation and fetch so a
|
|
450
|
+
// protocol mismatch has the same zero-side-effect contract as an unconfigured model.
|
|
451
|
+
if (
|
|
452
|
+
model === null ||
|
|
453
|
+
matchedProviderConfig === null ||
|
|
454
|
+
!providerSupportsApiRoute(route, matchedProviderConfig)
|
|
455
|
+
) {
|
|
456
|
+
logger.warn(`[handler] Unsupported provider: model=${model}`);
|
|
457
|
+
return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
|
|
458
|
+
}
|
|
459
|
+
|
|
364
460
|
const upstreamBase = selectUpstreamBase(route, matchedProviderConfig);
|
|
365
461
|
const upstreamUrl = buildUpstreamUrl(upstreamBase, parsed.normalizedPath);
|
|
366
462
|
|
|
@@ -374,15 +470,7 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
|
|
|
374
470
|
setUpstreamHost(upstreamHeaders, upstreamBase);
|
|
375
471
|
injectProviderAuth(upstreamHeaders, matchedProviderConfig);
|
|
376
472
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// Only proxy requests matching a registered provider
|
|
380
|
-
if (model === null || provider === null) {
|
|
381
|
-
logger.warn(`[handler] Unsupported provider: model=${model}`);
|
|
382
|
-
return new Response("Forbidden: unsupported provider", { status: STATUS_FORBIDDEN });
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Pick the format handler from the request path. provider.format is only a
|
|
473
|
+
// Pick the format handler from the request path. ProviderConfig.format is only a
|
|
386
474
|
// hint used as a fallback when no anthropicBaseUrl/openaiBaseUrl is set, and
|
|
387
475
|
// does NOT describe what the upstream actually returns: a provider configured
|
|
388
476
|
// with format="openai" may still expose an Anthropic-compatible endpoint
|
|
@@ -441,7 +529,7 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
|
|
|
441
529
|
log.responseStatus = status;
|
|
442
530
|
log.responseText = fetchResult.reason;
|
|
443
531
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
|
|
444
|
-
|
|
532
|
+
await persistFinalizedLogUpdate(log);
|
|
445
533
|
return new Response(
|
|
446
534
|
fetchResult.kind === "network-policy"
|
|
447
535
|
? "Forbidden: upstream URL is not permitted"
|
|
@@ -458,14 +546,14 @@ async function handleAdmittedProxy(req: Request): Promise<Response> {
|
|
|
458
546
|
log.responseStatus = 499; // Client Closed Request (non-standard but descriptive)
|
|
459
547
|
log.responseText = "Client aborted";
|
|
460
548
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: "Client aborted" });
|
|
461
|
-
|
|
549
|
+
await persistFinalizedLogUpdate(log);
|
|
462
550
|
return new Response("Client aborted", { status: 499 });
|
|
463
551
|
}
|
|
464
552
|
logger.error(`[handler] Proxy error: ${req.method} ${parsed.apiPath}`, String(err));
|
|
465
553
|
log.responseStatus = STATUS_BAD_GATEWAY;
|
|
466
554
|
log.responseText = String(err);
|
|
467
555
|
appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: String(err) });
|
|
468
|
-
|
|
556
|
+
await persistFinalizedLogUpdate(log);
|
|
469
557
|
return new Response(`Proxy error: ${err}`, { status: STATUS_BAD_GATEWAY });
|
|
470
558
|
}
|
|
471
559
|
|
|
@@ -527,6 +615,18 @@ function responseWithRuntimeLease(response: Response, lease: RuntimeRequestLease
|
|
|
527
615
|
export async function handleProxy(req: Request): Promise<Response> {
|
|
528
616
|
const lease = acquireRuntimeRequest();
|
|
529
617
|
if (lease === null) {
|
|
618
|
+
const admission = getRuntimeAdmissionState();
|
|
619
|
+
if (admission.acceptingRequests) {
|
|
620
|
+
return resourceLimitResponse({
|
|
621
|
+
error: `Proxy request capacity reached. At most ${String(admission.capacity)} active requests are allowed; retry after a response completes.`,
|
|
622
|
+
code: "proxy-capacity-exceeded",
|
|
623
|
+
status: 503,
|
|
624
|
+
resource: "proxy-requests",
|
|
625
|
+
limit: admission.capacity,
|
|
626
|
+
retryable: true,
|
|
627
|
+
retryAfterSeconds: 1,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
530
630
|
return Response.json(
|
|
531
631
|
{ error: "Agent Inspector is shutting down" },
|
|
532
632
|
{ status: 503, headers: { "cache-control": "no-store", "retry-after": "5" } },
|