@tonyclaw/agent-inspector 2.0.0
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/cli.js +1611 -0
- package/.output/nitro.json +17 -0
- package/.output/public/assets/CompareDrawer-CU5ZrWcL.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-pEBqVp1d.js +101 -0
- package/.output/public/assets/ReplayDialog-F58yNg5j.js +1 -0
- package/.output/public/assets/RequestAnatomy-C9lT0qE_.js +1 -0
- package/.output/public/assets/ResponseView-DHJq6bnz.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-BTgfpFUT.js +1 -0
- package/.output/public/assets/_sessionId-DsNRbnNm.js +1 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-CpWG2hFn.css +1 -0
- package/.output/public/assets/index-DmBV8Gve.js +1 -0
- package/.output/public/assets/json-viewer-CZVYLR8j.js +14 -0
- package/.output/public/assets/main-DHs7FBK3.js +18 -0
- package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
- package/.output/server/_chunks/ssr-renderer.mjs +22 -0
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +2 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +2 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +2 -0
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
- package/.output/server/_libs/ajv-formats.mjs +330 -0
- package/.output/server/_libs/ajv.mjs +11444 -0
- package/.output/server/_libs/aria-hidden.mjs +122 -0
- package/.output/server/_libs/atomically.mjs +152 -0
- package/.output/server/_libs/bail.mjs +8 -0
- package/.output/server/_libs/cfworker__json-schema.mjs +1 -0
- package/.output/server/_libs/character-entities.mjs +2130 -0
- package/.output/server/_libs/class-variance-authority.mjs +44 -0
- package/.output/server/_libs/clsx.mjs +16 -0
- package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
- package/.output/server/_libs/conf.mjs +635 -0
- package/.output/server/_libs/cookie-es.mjs +58 -0
- package/.output/server/_libs/core-util-is.mjs +75 -0
- package/.output/server/_libs/croner.mjs +1 -0
- package/.output/server/_libs/crossws.mjs +1 -0
- package/.output/server/_libs/debounce-fn.mjs +69 -0
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
- package/.output/server/_libs/dequal.mjs +27 -0
- package/.output/server/_libs/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -0
- package/.output/server/_libs/diff.mjs +320 -0
- package/.output/server/_libs/dot-prop.mjs +265 -0
- package/.output/server/_libs/env-paths.mjs +57 -0
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
- package/.output/server/_libs/extend.mjs +97 -0
- package/.output/server/_libs/fast-deep-equal.mjs +38 -0
- package/.output/server/_libs/fast-uri.mjs +812 -0
- package/.output/server/_libs/floating-ui__core.mjs +725 -0
- package/.output/server/_libs/floating-ui__dom.mjs +622 -0
- package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
- package/.output/server/_libs/floating-ui__utils.mjs +320 -0
- package/.output/server/_libs/get-nonce.mjs +9 -0
- package/.output/server/_libs/h3-v2.mjs +276 -0
- package/.output/server/_libs/h3.mjs +408 -0
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
- package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
- package/.output/server/_libs/hookable.mjs +1 -0
- package/.output/server/_libs/html-url-attributes.mjs +26 -0
- package/.output/server/_libs/immediate.mjs +74 -0
- package/.output/server/_libs/inherits.mjs +50 -0
- package/.output/server/_libs/inline-style-parser.mjs +142 -0
- package/.output/server/_libs/is-plain-obj.mjs +10 -0
- package/.output/server/_libs/isarray.mjs +14 -0
- package/.output/server/_libs/isbot.mjs +20 -0
- package/.output/server/_libs/json-schema-traverse.mjs +180 -0
- package/.output/server/_libs/jszip.mjs +3051 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +492 -0
- package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
- package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
- package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
- package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
- package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
- package/.output/server/_libs/micromark-factory-label.mjs +63 -0
- package/.output/server/_libs/micromark-factory-space.mjs +24 -0
- package/.output/server/_libs/micromark-factory-title.mjs +65 -0
- package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
- package/.output/server/_libs/micromark-util-character.mjs +44 -0
- package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
- package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
- package/.output/server/_libs/micromark-util-encode.mjs +1 -0
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
- package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
- package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
- package/.output/server/_libs/micromark.mjs +906 -0
- package/.output/server/_libs/mimic-function.mjs +47 -0
- package/.output/server/_libs/modelcontextprotocol__server.mjs +9738 -0
- package/.output/server/_libs/ocache.mjs +1 -0
- package/.output/server/_libs/ohash.mjs +1 -0
- package/.output/server/_libs/pako.mjs +4223 -0
- package/.output/server/_libs/process-nextick-args.mjs +48 -0
- package/.output/server/_libs/property-information.mjs +1209 -0
- package/.output/server/_libs/radix-ui.mjs +1 -0
- package/.output/server/_libs/radix-ui__number.mjs +6 -0
- package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
- package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
- package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
- package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
- package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
- package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
- package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
- package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
- package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
- package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
- package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
- package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
- package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
- package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
- package/.output/server/_libs/radix-ui__react-toast.mjs +2 -0
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
- package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
- package/.output/server/_libs/react-dom.mjs +10781 -0
- package/.output/server/_libs/react-markdown.mjs +147 -0
- package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/.output/server/_libs/react-remove-scroll.mjs +328 -0
- package/.output/server/_libs/react-style-singleton.mjs +69 -0
- package/.output/server/_libs/react.mjs +515 -0
- package/.output/server/_libs/readable-stream.mjs +1518 -0
- package/.output/server/_libs/remark-parse.mjs +19 -0
- package/.output/server/_libs/remark-rehype.mjs +21 -0
- package/.output/server/_libs/rou3.mjs +14 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1938 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +152 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +1029 -0
- package/.output/server/_libs/stubborn-fs.mjs +91 -0
- package/.output/server/_libs/stubborn-utils.mjs +66 -0
- package/.output/server/_libs/style-to-js.mjs +72 -0
- package/.output/server/_libs/style-to-object.mjs +38 -0
- package/.output/server/_libs/swr.mjs +939 -0
- package/.output/server/_libs/tailwind-merge.mjs +3010 -0
- package/.output/server/_libs/tanstack__history.mjs +217 -0
- package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
- package/.output/server/_libs/tanstack__react-store.mjs +1 -0
- package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
- package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
- package/.output/server/_libs/tanstack__store.mjs +1 -0
- package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
- package/.output/server/_libs/tiny-invariant.mjs +12 -0
- package/.output/server/_libs/tiny-warning.mjs +5 -0
- package/.output/server/_libs/trim-lines.mjs +41 -0
- package/.output/server/_libs/trough.mjs +85 -0
- package/.output/server/_libs/tslib.mjs +1 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -0
- package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
- package/.output/server/_libs/unified.mjs +661 -0
- package/.output/server/_libs/unist-util-is.mjs +100 -0
- package/.output/server/_libs/unist-util-position.mjs +27 -0
- package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
- package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
- package/.output/server/_libs/unist-util-visit.mjs +24 -0
- package/.output/server/_libs/unstorage.mjs +1 -0
- package/.output/server/_libs/use-callback-ref.mjs +66 -0
- package/.output/server/_libs/use-sidecar.mjs +106 -0
- package/.output/server/_libs/use-sync-external-store.mjs +64 -0
- package/.output/server/_libs/util-deprecate.mjs +12 -0
- package/.output/server/_libs/vfile-message.mjs +138 -0
- package/.output/server/_libs/vfile.mjs +467 -0
- package/.output/server/_libs/when-exit.mjs +53 -0
- package/.output/server/_libs/zod.mjs +4524 -0
- package/.output/server/_sessionId-wMLPvC5g.mjs +123 -0
- package/.output/server/_ssr/CompareDrawer-BU4V0uVf.mjs +1041 -0
- package/.output/server/_ssr/ProxyViewerContainer-BnRwFEnn.mjs +5972 -0
- package/.output/server/_ssr/ReplayDialog-C7dn9pd_.mjs +322 -0
- package/.output/server/_ssr/RequestAnatomy-C1rWpe9-.mjs +353 -0
- package/.output/server/_ssr/ResponseView-hGpPaYsf.mjs +602 -0
- package/.output/server/_ssr/StreamingChunkSequence-BRWI1r_G.mjs +302 -0
- package/.output/server/_ssr/index-BKURLVPz.mjs +118 -0
- package/.output/server/_ssr/index.mjs +1184 -0
- package/.output/server/_ssr/json-viewer-BBd2DtQP.mjs +515 -0
- package/.output/server/_ssr/router-BcZ0D6AB.mjs +6317 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-1y8ZVxRI.mjs +4 -0
- package/.output/server/index.mjs +436 -0
- package/.output/server/node_modules/tslib/modules/index.js +70 -0
- package/.output/server/node_modules/tslib/modules/package.json +3 -0
- package/.output/server/node_modules/tslib/package.json +47 -0
- package/.output/server/node_modules/tslib/tslib.js +484 -0
- package/.output/server/package.json +9 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/package.json +110 -0
- package/src/assets/favicon.svg +31 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/claude-code.svg +4 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/opencode.svg +4 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli/detect-tools.ts +147 -0
- package/src/cli/doctor.ts +521 -0
- package/src/cli/onboard.ts +224 -0
- package/src/cli/templates/command-onboard.ts +17 -0
- package/src/cli/templates/skill-onboard.ts +547 -0
- package/src/cli.ts +345 -0
- package/src/components/OnboardingBanner.tsx +67 -0
- package/src/components/ProxyViewer.tsx +545 -0
- package/src/components/ProxyViewerContainer.tsx +363 -0
- package/src/components/providers/ImportWizardDialog.tsx +349 -0
- package/src/components/providers/ProviderCard.tsx +474 -0
- package/src/components/providers/ProviderForm.tsx +494 -0
- package/src/components/providers/ProviderLogo.tsx +117 -0
- package/src/components/providers/ProvidersPanel.tsx +619 -0
- package/src/components/providers/SettingsDialog.tsx +202 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +893 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +107 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +300 -0
- package/src/components/proxy-viewer/LogEntry.tsx +543 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +501 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +218 -0
- package/src/components/proxy-viewer/ResponseView.tsx +171 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +188 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +136 -0
- package/src/components/proxy-viewer/TurnGroup.tsx +337 -0
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
- package/src/components/proxy-viewer/anatomy/types.ts +39 -0
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
- package/src/components/proxy-viewer/cacheTrend.ts +50 -0
- package/src/components/proxy-viewer/diff/DiffView.tsx +321 -0
- package/src/components/proxy-viewer/diff/computeDiff.ts +178 -0
- package/src/components/proxy-viewer/diff/index.ts +3 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +157 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +66 -0
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
- package/src/components/proxy-viewer/formats/index.tsx +33 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +170 -0
- package/src/components/proxy-viewer/index.ts +9 -0
- package/src/components/proxy-viewer/lazy.ts +37 -0
- package/src/components/proxy-viewer/log-formats/anthropic.ts +194 -0
- package/src/components/proxy-viewer/log-formats/index.ts +23 -0
- package/src/components/proxy-viewer/log-formats/openai.ts +167 -0
- package/src/components/proxy-viewer/log-formats/types.ts +40 -0
- package/src/components/proxy-viewer/log-formats/unknown.ts +18 -0
- package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
- package/src/components/proxy-viewer/requestDiff.ts +277 -0
- package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
- package/src/components/proxy-viewer/viewerState.ts +66 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/confirm-dialog.tsx +51 -0
- package/src/components/ui/crab-logo.tsx +95 -0
- package/src/components/ui/crab-variants.tsx +467 -0
- package/src/components/ui/dialog.tsx +129 -0
- package/src/components/ui/json-expansion-button.tsx +56 -0
- package/src/components/ui/json-viewer-bulk.ts +97 -0
- package/src/components/ui/json-viewer.tsx +494 -0
- package/src/components/ui/mcp-logo.tsx +20 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +178 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/index.css +11 -0
- package/src/knowledge/candidateStore.ts +63 -0
- package/src/knowledge/distiller.ts +98 -0
- package/src/knowledge/openclawClient.ts +118 -0
- package/src/knowledge/redactor.ts +80 -0
- package/src/knowledge/types.ts +84 -0
- package/src/lib/apiClient.ts +49 -0
- package/src/lib/export-logs.ts +51 -0
- package/src/lib/mask.ts +4 -0
- package/src/lib/objectUtils.ts +22 -0
- package/src/lib/providerContract.ts +26 -0
- package/src/lib/providerTestContract.ts +107 -0
- package/src/lib/runtimeConfig.ts +25 -0
- package/src/lib/serverPort.ts +41 -0
- package/src/lib/sessionUrl.ts +44 -0
- package/src/lib/stopReason.ts +58 -0
- package/src/lib/useOnboarding.ts +80 -0
- package/src/lib/useProviders.ts +30 -0
- package/src/lib/useStripConfig.ts +108 -0
- package/src/lib/utils.ts +21 -0
- package/src/mcp/loopback.ts +76 -0
- package/src/mcp/previewExtractor.ts +166 -0
- package/src/mcp/server.ts +396 -0
- package/src/mcp/toolHandlers.ts +341 -0
- package/src/proxy/chunkStorage.ts +112 -0
- package/src/proxy/claudeCodeStrip.ts +99 -0
- package/src/proxy/config.ts +172 -0
- package/src/proxy/constants.ts +47 -0
- package/src/proxy/dataDir.ts +86 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +71 -0
- package/src/proxy/formats/anthropic/index.ts +14 -0
- package/src/proxy/formats/anthropic/register.ts +4 -0
- package/src/proxy/formats/anthropic/schemas.ts +237 -0
- package/src/proxy/formats/anthropic/stream.ts +205 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +36 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +96 -0
- package/src/proxy/formats/openai/index.ts +25 -0
- package/src/proxy/formats/openai/provider.ts +50 -0
- package/src/proxy/formats/openai/register.ts +4 -0
- package/src/proxy/formats/openai/schemas.ts +187 -0
- package/src/proxy/formats/openai/stream.ts +206 -0
- package/src/proxy/formats/protocol.ts +50 -0
- package/src/proxy/formats/providerRegistry.ts +51 -0
- package/src/proxy/formats/providers/index.ts +3 -0
- package/src/proxy/formats/registry.ts +66 -0
- package/src/proxy/handler.ts +334 -0
- package/src/proxy/logFinalizer.ts +305 -0
- package/src/proxy/logFinalizer.worker.ts +24 -0
- package/src/proxy/logIndex.ts +268 -0
- package/src/proxy/logger.ts +179 -0
- package/src/proxy/openaiOrphanToolStrip.ts +142 -0
- package/src/proxy/providerImporters.ts +491 -0
- package/src/proxy/providers.ts +613 -0
- package/src/proxy/schemas.ts +209 -0
- package/src/proxy/sessionProcess.ts +140 -0
- package/src/proxy/sessionRuntime.ts +85 -0
- package/src/proxy/sessionSupervisor.ts +283 -0
- package/src/proxy/sessionWorkerEntry.ts +26 -0
- package/src/proxy/socketTracker.ts +255 -0
- package/src/proxy/store.ts +412 -0
- package/src/proxy/upstream.ts +90 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +45 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/config.ts +53 -0
- package/src/routes/api/health.ts +15 -0
- package/src/routes/api/knowledge.candidates.$candidateId.promote.ts +32 -0
- package/src/routes/api/knowledge.candidates.ts +10 -0
- package/src/routes/api/knowledge.project-context.ts +18 -0
- package/src/routes/api/knowledge.search.ts +31 -0
- package/src/routes/api/knowledge.sessions.$sessionId.candidates.ts +16 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +191 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +74 -0
- package/src/routes/api/logs.ts +59 -0
- package/src/routes/api/mcp.ts +25 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.test.log.ts +293 -0
- package/src/routes/api/providers.$providerId.ts +50 -0
- package/src/routes/api/providers.export.ts +26 -0
- package/src/routes/api/providers.import.ts +47 -0
- package/src/routes/api/providers.scan.ts +23 -0
- package/src/routes/api/providers.ts +45 -0
- package/src/routes/api/sessions.ts +17 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/src/routes/session/$sessionId.tsx +23 -0
- package/styles/globals.css +188 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Import JsonValueSchema from shared location to avoid circular dependency
|
|
4
|
+
import { JsonValueSchema, type JsonValue } from "./formats/jsonSchema";
|
|
5
|
+
|
|
6
|
+
export { JsonValueSchema };
|
|
7
|
+
export type { JsonValue };
|
|
8
|
+
|
|
9
|
+
export type RequestFormat = "anthropic" | "openai" | "unknown";
|
|
10
|
+
|
|
11
|
+
import { safeGetOwnProperty } from "../lib/objectUtils";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A single SSE event from a streaming response.
|
|
15
|
+
*/
|
|
16
|
+
export const StreamingChunkSchema = z.object({
|
|
17
|
+
index: z.number(),
|
|
18
|
+
timestamp: z.number(),
|
|
19
|
+
type: z.string(),
|
|
20
|
+
data: JsonValueSchema,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type StreamingChunk = z.infer<typeof StreamingChunkSchema>;
|
|
24
|
+
|
|
25
|
+
const StreamingChunksArraySchema = z.object({
|
|
26
|
+
chunks: z.array(StreamingChunkSchema),
|
|
27
|
+
truncated: z.boolean().optional().default(false),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const CapturedLogSchema = z.object({
|
|
31
|
+
id: z.number(),
|
|
32
|
+
timestamp: z.string(),
|
|
33
|
+
method: z.string(),
|
|
34
|
+
path: z.string(),
|
|
35
|
+
model: z.string().nullable(),
|
|
36
|
+
sessionId: z.string().nullable(),
|
|
37
|
+
rawRequestBody: z.string().nullable(),
|
|
38
|
+
responseStatus: z.number().nullable(),
|
|
39
|
+
responseText: z.string().nullable(),
|
|
40
|
+
inputTokens: z.number().nullable(),
|
|
41
|
+
outputTokens: z.number().nullable(),
|
|
42
|
+
cacheCreationInputTokens: z.number().nullable(),
|
|
43
|
+
cacheReadInputTokens: z.number().nullable(),
|
|
44
|
+
elapsedMs: z.number().nullable(),
|
|
45
|
+
streaming: z.boolean(),
|
|
46
|
+
userAgent: z.string().nullable(),
|
|
47
|
+
origin: z.string().nullable(),
|
|
48
|
+
rawHeaders: z.record(z.string(), z.string()).optional(),
|
|
49
|
+
/** Headers sent to the upstream LLM */
|
|
50
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
51
|
+
apiFormat: z.enum(["anthropic", "openai", "unknown"]).default("unknown"),
|
|
52
|
+
isTest: z.boolean().optional().default(false),
|
|
53
|
+
providerName: z.string().nullable().optional(),
|
|
54
|
+
clientPort: z.number().nullable().optional(),
|
|
55
|
+
clientPid: z.number().nullable().optional(),
|
|
56
|
+
clientCwd: z.string().nullable().optional(),
|
|
57
|
+
clientProjectFolder: z.string().nullable().optional(),
|
|
58
|
+
streamingChunks: StreamingChunksArraySchema.optional(),
|
|
59
|
+
streamingChunksPath: z.string().nullable().optional(),
|
|
60
|
+
/** Error message from streaming response (e.g., SSE error event) */
|
|
61
|
+
error: z.string().nullable().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type CapturedLog = z.infer<typeof CapturedLogSchema>;
|
|
65
|
+
|
|
66
|
+
export type TokenUsage = {
|
|
67
|
+
inputTokens: number | null;
|
|
68
|
+
outputTokens: number | null;
|
|
69
|
+
cacheCreationInputTokens: number | null;
|
|
70
|
+
cacheReadInputTokens: number | null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ============================================================
|
|
74
|
+
// Import types and schemas from formats
|
|
75
|
+
// ============================================================
|
|
76
|
+
|
|
77
|
+
// Import types and schemas from formats for internal use
|
|
78
|
+
import { InspectorRequestSchema, type InspectorRequest } from "./formats/anthropic/schemas";
|
|
79
|
+
|
|
80
|
+
// Re-export types and schemas from formats for backward compatibility
|
|
81
|
+
export {
|
|
82
|
+
InspectorRequestSchema,
|
|
83
|
+
InspectorResponseSchema,
|
|
84
|
+
SseEventSchema,
|
|
85
|
+
} from "./formats/anthropic/schemas";
|
|
86
|
+
export type {
|
|
87
|
+
InspectorRequest,
|
|
88
|
+
InspectorResponse,
|
|
89
|
+
ResponseContentBlockType,
|
|
90
|
+
} from "./formats/anthropic/schemas";
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
OpenAIRequestSchema,
|
|
94
|
+
OpenAIResponseSchema,
|
|
95
|
+
OpenAISSERawChunkSchema,
|
|
96
|
+
type OpenAIResponse,
|
|
97
|
+
type OpenAIToolCall,
|
|
98
|
+
parseOpenAIResponse,
|
|
99
|
+
} from "./formats/openai/schemas";
|
|
100
|
+
|
|
101
|
+
// ============================================================
|
|
102
|
+
// Utility functions
|
|
103
|
+
// ============================================================
|
|
104
|
+
|
|
105
|
+
function detectFormat(rawBody: string | null): RequestFormat {
|
|
106
|
+
if (rawBody === null) return "unknown";
|
|
107
|
+
try {
|
|
108
|
+
const json: unknown = JSON.parse(rawBody);
|
|
109
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
110
|
+
const hasModel = safeGetOwnProperty(json, "model") !== undefined;
|
|
111
|
+
const hasMessages = safeGetOwnProperty(json, "messages") !== undefined;
|
|
112
|
+
if (hasModel && hasMessages) {
|
|
113
|
+
// Anthropic requests put `system` as a top-level key; OpenAI does not.
|
|
114
|
+
// Both formats can have `tools`, so we check `system` as the discriminator.
|
|
115
|
+
if (safeGetOwnProperty(json, "system") !== undefined) {
|
|
116
|
+
return "anthropic";
|
|
117
|
+
}
|
|
118
|
+
const msgVal = safeGetOwnProperty(json, "messages");
|
|
119
|
+
if (Array.isArray(msgVal)) {
|
|
120
|
+
return "openai";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return "unknown";
|
|
125
|
+
} catch {
|
|
126
|
+
return "unknown";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Schema for model name in request body (used for routing)
|
|
131
|
+
const RequestModelSchema = z.object({
|
|
132
|
+
model: z.string(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export function extractModelFromBody(body: string): string | null {
|
|
136
|
+
try {
|
|
137
|
+
const json: unknown = JSON.parse(body);
|
|
138
|
+
const parsed = RequestModelSchema.safeParse(json);
|
|
139
|
+
if (parsed.success) {
|
|
140
|
+
return parsed.data.model;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Loose request schema used for routing + log metadata. Tolerant of missing
|
|
150
|
+
* fields so it can extract what it can from a partially-valid or unparseable
|
|
151
|
+
* body without throwing.
|
|
152
|
+
*/
|
|
153
|
+
export const LooseRequestSchema = z.object({
|
|
154
|
+
model: z.string().optional(),
|
|
155
|
+
metadata: z.object({ user_id: z.string().optional() }).passthrough().optional(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export type RequestMetadata = {
|
|
159
|
+
model: string | null;
|
|
160
|
+
sessionId: string | null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function extractSessionIdFromHeaders(headers: Headers): string | null {
|
|
164
|
+
return headers.get("x-agent-inspector-session-id") ?? headers.get("x-session-affinity");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse a request body exactly once and pull out everything the handler needs
|
|
169
|
+
* for routing + log creation: the model (used for provider selection) and the
|
|
170
|
+
* session id (from `metadata.user_id` or the `x-session-affinity` header).
|
|
171
|
+
*
|
|
172
|
+
* Returning `null` for either field is a valid outcome — callers must handle
|
|
173
|
+
* "unknown" gracefully. Callers that already have the body string should use
|
|
174
|
+
* this instead of re-parsing with `extractModelFromBody` and a second pass
|
|
175
|
+
* for the session id.
|
|
176
|
+
*/
|
|
177
|
+
export function extractRequestMetadata(body: string | null, headers: Headers): RequestMetadata {
|
|
178
|
+
const headerSessionId = extractSessionIdFromHeaders(headers);
|
|
179
|
+
if (body === null) return { model: null, sessionId: headerSessionId };
|
|
180
|
+
try {
|
|
181
|
+
const json: unknown = JSON.parse(body);
|
|
182
|
+
const loose = LooseRequestSchema.safeParse(json);
|
|
183
|
+
if (loose.success) {
|
|
184
|
+
return {
|
|
185
|
+
model: loose.data.model ?? null,
|
|
186
|
+
sessionId: loose.data.metadata?.user_id ?? headerSessionId,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// body not valid JSON
|
|
191
|
+
}
|
|
192
|
+
return { model: null, sessionId: headerSessionId };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse a raw request body into an InspectorRequest.
|
|
197
|
+
* @deprecated Use FormatHandler.parseRequest() instead
|
|
198
|
+
*/
|
|
199
|
+
export function parseRequest(rawBody: string | null): InspectorRequest | null {
|
|
200
|
+
if (rawBody === null) return null;
|
|
201
|
+
try {
|
|
202
|
+
const json: unknown = JSON.parse(rawBody);
|
|
203
|
+
const result = InspectorRequestSchema.safeParse(json);
|
|
204
|
+
if (result.success) return result.data;
|
|
205
|
+
return null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { logger } from "./logger";
|
|
5
|
+
import type { FinalizeLogJob, FinalizeLogResult } from "./logFinalizer";
|
|
6
|
+
|
|
7
|
+
const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1000; // 5 min default
|
|
8
|
+
const MAX_RESTARTS = 3;
|
|
9
|
+
|
|
10
|
+
const _processes = new Map<string, SessionProcess>();
|
|
11
|
+
|
|
12
|
+
type PendingJob = {
|
|
13
|
+
resolve: (result: FinalizeLogResult) => void;
|
|
14
|
+
reject: (err: Error) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function resolveSessionWorkerPath(): string {
|
|
18
|
+
return fileURLToPath(new URL("./sessionWorkerEntry.ts", import.meta.url));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isSessionProcessAvailable(): boolean {
|
|
22
|
+
return existsSync(resolveSessionWorkerPath());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class SessionProcess {
|
|
26
|
+
private child: ChildProcess | null = null;
|
|
27
|
+
private pending = new Map<string, PendingJob>();
|
|
28
|
+
private nextId = 0;
|
|
29
|
+
private idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
30
|
+
private restartCount = 0;
|
|
31
|
+
private destroyed = false;
|
|
32
|
+
|
|
33
|
+
constructor(private sessionId: string) {}
|
|
34
|
+
|
|
35
|
+
/** Number of outstanding jobs sent to this process. */
|
|
36
|
+
get pendingCount(): number {
|
|
37
|
+
return this.pending.size;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private ensureRunning(): ChildProcess {
|
|
41
|
+
if (this.child !== null && this.child.connected) return this.child;
|
|
42
|
+
|
|
43
|
+
const resolvedPath = resolveSessionWorkerPath();
|
|
44
|
+
|
|
45
|
+
this.child = spawn(process.execPath, [...process.execArgv, resolvedPath], {
|
|
46
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.restartCount += 1;
|
|
51
|
+
|
|
52
|
+
this.child.on("message", (msg: { id: string; result: FinalizeLogResult }) => {
|
|
53
|
+
const pending = this.pending.get(msg.id);
|
|
54
|
+
if (pending !== undefined) {
|
|
55
|
+
this.pending.delete(msg.id);
|
|
56
|
+
pending.resolve(msg.result);
|
|
57
|
+
}
|
|
58
|
+
this.resetIdleTimer();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.child.on("error", (err) => {
|
|
62
|
+
logger.error(`[sessionProcess] Session ${this.sessionId} process error:`, err.message);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.child.on("exit", (code, signal) => {
|
|
66
|
+
const wasConnected = this.child !== null;
|
|
67
|
+
this.child = null;
|
|
68
|
+
|
|
69
|
+
if (this.destroyed) return;
|
|
70
|
+
|
|
71
|
+
// Reject all pending jobs on unexpected exit
|
|
72
|
+
for (const [, pending] of this.pending) {
|
|
73
|
+
pending.reject(
|
|
74
|
+
new Error(
|
|
75
|
+
`Session process exited with code ${code ?? signal}, session=${this.sessionId}`,
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
this.pending.clear();
|
|
80
|
+
|
|
81
|
+
if (wasConnected && this.restartCount <= MAX_RESTARTS) {
|
|
82
|
+
logger.warn(
|
|
83
|
+
`[sessionProcess] Session ${this.sessionId} worker exited (code=${code ?? signal}), ` +
|
|
84
|
+
`restart ${this.restartCount}/${MAX_RESTARTS}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.resetIdleTimer();
|
|
90
|
+
return this.child;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
enqueue(job: FinalizeLogJob): Promise<FinalizeLogResult> {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const child = this.ensureRunning();
|
|
96
|
+
const id = String(++this.nextId);
|
|
97
|
+
this.pending.set(id, { resolve, reject });
|
|
98
|
+
child.send({ id, job });
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private resetIdleTimer(): void {
|
|
103
|
+
if (this.idleTimer !== null) clearTimeout(this.idleTimer);
|
|
104
|
+
this.idleTimer = setTimeout(() => {
|
|
105
|
+
if (this.pending.size === 0) {
|
|
106
|
+
this.destroy();
|
|
107
|
+
}
|
|
108
|
+
}, IDLE_TIMEOUT_MS);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
destroy(): void {
|
|
112
|
+
this.destroyed = true;
|
|
113
|
+
if (this.idleTimer !== null) {
|
|
114
|
+
clearTimeout(this.idleTimer);
|
|
115
|
+
this.idleTimer = null;
|
|
116
|
+
}
|
|
117
|
+
if (this.child !== null) {
|
|
118
|
+
this.child.kill();
|
|
119
|
+
this.child = null;
|
|
120
|
+
}
|
|
121
|
+
_processes.delete(this.sessionId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Get or create a dedicated child process for a session. */
|
|
126
|
+
export function getSessionProcess(sessionId: string): SessionProcess {
|
|
127
|
+
const existing = _processes.get(sessionId);
|
|
128
|
+
if (existing !== undefined) return existing;
|
|
129
|
+
|
|
130
|
+
const sp = new SessionProcess(sessionId);
|
|
131
|
+
_processes.set(sessionId, sp);
|
|
132
|
+
return sp;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Forcefully tear down all session processes (e.g. on server shutdown). */
|
|
136
|
+
export function destroyAllSessionProcesses(): void {
|
|
137
|
+
for (const [, sp] of _processes) {
|
|
138
|
+
sp.destroy();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { logger } from "./logger";
|
|
2
|
+
import { executeFinalizeLogJob, type FinalizeLogJob } from "./logFinalizer";
|
|
3
|
+
import type { CapturedLog } from "./schemas";
|
|
4
|
+
import {
|
|
5
|
+
getLogSessionId,
|
|
6
|
+
markSessionTaskFinished,
|
|
7
|
+
markSessionTaskQueued,
|
|
8
|
+
markSessionTaskStarted,
|
|
9
|
+
} from "./sessionSupervisor";
|
|
10
|
+
|
|
11
|
+
const UNASSIGNED_SESSION_QUEUE = "__unassigned__";
|
|
12
|
+
|
|
13
|
+
export type SessionTaskName = "finalize-log";
|
|
14
|
+
|
|
15
|
+
type SessionTask<T> = () => Promise<T> | T;
|
|
16
|
+
|
|
17
|
+
const queues = new Map<string, Promise<void>>();
|
|
18
|
+
|
|
19
|
+
function queueKeyForSession(sessionId: string | null): string {
|
|
20
|
+
return sessionId ?? UNASSIGNED_SESSION_QUEUE;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function errorMessage(err: unknown): string {
|
|
24
|
+
return err instanceof Error ? err.message : String(err);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runSessionTask<T>(
|
|
28
|
+
sessionId: string | null,
|
|
29
|
+
taskName: SessionTaskName,
|
|
30
|
+
task: SessionTask<T>,
|
|
31
|
+
): Promise<T> {
|
|
32
|
+
markSessionTaskStarted(sessionId);
|
|
33
|
+
try {
|
|
34
|
+
const result = await task();
|
|
35
|
+
markSessionTaskFinished(sessionId, null);
|
|
36
|
+
return result;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const message = errorMessage(err);
|
|
39
|
+
logger.error(`[sessionRuntime] ${taskName} failed for session ${sessionId ?? "unassigned"}`);
|
|
40
|
+
markSessionTaskFinished(sessionId, message);
|
|
41
|
+
return Promise.reject(err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function enqueueSessionTask<T>(
|
|
46
|
+
sessionId: string | null,
|
|
47
|
+
taskName: SessionTaskName,
|
|
48
|
+
task: SessionTask<T>,
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
const queueKey = queueKeyForSession(sessionId);
|
|
51
|
+
const previous = queues.get(queueKey) ?? Promise.resolve();
|
|
52
|
+
|
|
53
|
+
markSessionTaskQueued(sessionId);
|
|
54
|
+
|
|
55
|
+
const current = previous
|
|
56
|
+
.catch(() => undefined)
|
|
57
|
+
.then(() => runSessionTask(sessionId, taskName, task));
|
|
58
|
+
const settled = current.then(
|
|
59
|
+
() => undefined,
|
|
60
|
+
() => undefined,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
queues.set(queueKey, settled);
|
|
64
|
+
void settled.then(() => {
|
|
65
|
+
if (queues.get(queueKey) === settled) {
|
|
66
|
+
queues.delete(queueKey);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function enqueueLogTask<T>(
|
|
74
|
+
log: CapturedLog,
|
|
75
|
+
taskName: SessionTaskName,
|
|
76
|
+
task: SessionTask<T>,
|
|
77
|
+
): Promise<T> {
|
|
78
|
+
return enqueueSessionTask(getLogSessionId(log), taskName, task);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function enqueueFinalizeLogJob(job: FinalizeLogJob): Promise<void> {
|
|
82
|
+
return enqueueSessionTask(getLogSessionId(job.log), "finalize-log", () =>
|
|
83
|
+
executeFinalizeLogJob(job),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { CapturedLog } from "./schemas";
|
|
2
|
+
import { isSessionProcessAvailable } from "./sessionProcess";
|
|
3
|
+
|
|
4
|
+
export const PROVIDER_TEST_SESSION_ID = "provider-test";
|
|
5
|
+
|
|
6
|
+
export type SessionClientInfo = {
|
|
7
|
+
port: number | null;
|
|
8
|
+
pid: number | null;
|
|
9
|
+
cwd: string | null;
|
|
10
|
+
projectFolder: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type SessionSource = "explicit" | "client-process" | "client-connection" | "provider-test";
|
|
14
|
+
|
|
15
|
+
export type SessionIdentity = {
|
|
16
|
+
id: string | null;
|
|
17
|
+
source: SessionSource | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type SessionRuntimeMode = "in-process" | "worker-thread" | "child-process";
|
|
21
|
+
|
|
22
|
+
function getRuntimeMode(): SessionRuntimeMode {
|
|
23
|
+
if (process.env["FINALIZER_USE_WORKER"] === "0") return "in-process";
|
|
24
|
+
const mode = process.env["FINALIZER_RUNTIME"];
|
|
25
|
+
if (mode === "process") return "child-process";
|
|
26
|
+
if (mode === "worker") return "worker-thread";
|
|
27
|
+
if (mode === "inline") return "in-process";
|
|
28
|
+
return isSessionProcessAvailable() ? "child-process" : "in-process";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SessionSnapshot = {
|
|
32
|
+
id: string;
|
|
33
|
+
source: SessionSource;
|
|
34
|
+
runtimeMode: SessionRuntimeMode;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
requestCount: number;
|
|
38
|
+
activeRequests: number;
|
|
39
|
+
completedRequests: number;
|
|
40
|
+
failedRequests: number;
|
|
41
|
+
queuedTasks: number;
|
|
42
|
+
runningTasks: number;
|
|
43
|
+
lastTaskError: string | null;
|
|
44
|
+
lastLogId: number | null;
|
|
45
|
+
lastModel: string | null;
|
|
46
|
+
clientPid: number | null;
|
|
47
|
+
clientProjectFolder: string | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type SessionRecord = SessionSnapshot;
|
|
51
|
+
|
|
52
|
+
const sessions = new Map<string, SessionRecord>();
|
|
53
|
+
const knownLogSessions = new Map<number, string>();
|
|
54
|
+
const completedLogIds = new Set<number>();
|
|
55
|
+
const failedLogIds = new Set<number>();
|
|
56
|
+
|
|
57
|
+
function normalizeExplicitSessionId(sessionId: string | null | undefined): string | null {
|
|
58
|
+
if (sessionId === null || sessionId === undefined) return null;
|
|
59
|
+
const trimmed = sessionId.trim();
|
|
60
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildClientSessionId(clientInfo: SessionClientInfo): SessionIdentity {
|
|
64
|
+
const projectFolder = normalizeExplicitSessionId(clientInfo.projectFolder);
|
|
65
|
+
|
|
66
|
+
if (clientInfo.pid !== null) {
|
|
67
|
+
return {
|
|
68
|
+
id:
|
|
69
|
+
projectFolder !== null
|
|
70
|
+
? `process:${clientInfo.pid}:${projectFolder}`
|
|
71
|
+
: `process:${clientInfo.pid}`,
|
|
72
|
+
source: "client-process",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (clientInfo.port !== null) {
|
|
77
|
+
return {
|
|
78
|
+
id: `connection:${clientInfo.port}`,
|
|
79
|
+
source: "client-connection",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { id: null, source: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function resolveSessionIdentity(input: {
|
|
87
|
+
explicitSessionId: string | null;
|
|
88
|
+
clientInfo?: SessionClientInfo;
|
|
89
|
+
isTest?: boolean;
|
|
90
|
+
}): SessionIdentity {
|
|
91
|
+
if (input.isTest === true) {
|
|
92
|
+
return { id: PROVIDER_TEST_SESSION_ID, source: "provider-test" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const explicit = normalizeExplicitSessionId(input.explicitSessionId);
|
|
96
|
+
if (explicit !== null) {
|
|
97
|
+
return { id: explicit, source: "explicit" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (input.clientInfo !== undefined) {
|
|
101
|
+
return buildClientSessionId(input.clientInfo);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { id: null, source: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getLogSessionIdentity(log: CapturedLog): SessionIdentity {
|
|
108
|
+
return resolveSessionIdentity({
|
|
109
|
+
explicitSessionId: log.sessionId,
|
|
110
|
+
clientInfo: {
|
|
111
|
+
port: log.clientPort ?? null,
|
|
112
|
+
pid: log.clientPid ?? null,
|
|
113
|
+
cwd: log.clientCwd ?? null,
|
|
114
|
+
projectFolder: log.clientProjectFolder ?? null,
|
|
115
|
+
},
|
|
116
|
+
isTest: log.isTest,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getLogSessionId(log: CapturedLog): string | null {
|
|
121
|
+
return getLogSessionIdentity(log).id;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getTimestamp(log: CapturedLog): string {
|
|
125
|
+
return log.timestamp.length > 0 ? log.timestamp : new Date().toISOString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createSessionRecord(id: string, source: SessionSource, log: CapturedLog): SessionRecord {
|
|
129
|
+
const timestamp = getTimestamp(log);
|
|
130
|
+
return {
|
|
131
|
+
id,
|
|
132
|
+
source,
|
|
133
|
+
runtimeMode: getRuntimeMode(),
|
|
134
|
+
createdAt: timestamp,
|
|
135
|
+
updatedAt: timestamp,
|
|
136
|
+
requestCount: 0,
|
|
137
|
+
activeRequests: 0,
|
|
138
|
+
completedRequests: 0,
|
|
139
|
+
failedRequests: 0,
|
|
140
|
+
queuedTasks: 0,
|
|
141
|
+
runningTasks: 0,
|
|
142
|
+
lastTaskError: null,
|
|
143
|
+
lastLogId: null,
|
|
144
|
+
lastModel: null,
|
|
145
|
+
clientPid: log.clientPid ?? null,
|
|
146
|
+
clientProjectFolder: log.clientProjectFolder ?? null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function upsertSession(
|
|
151
|
+
log: CapturedLog,
|
|
152
|
+
sourceOverride?: SessionSource | null,
|
|
153
|
+
): SessionRecord | null {
|
|
154
|
+
const identity = getLogSessionIdentity(log);
|
|
155
|
+
if (identity.id === null) return null;
|
|
156
|
+
|
|
157
|
+
const source = sourceOverride ?? identity.source;
|
|
158
|
+
if (source === null) return null;
|
|
159
|
+
|
|
160
|
+
const existing = sessions.get(identity.id);
|
|
161
|
+
const session = existing ?? createSessionRecord(identity.id, source, log);
|
|
162
|
+
session.updatedAt = new Date().toISOString();
|
|
163
|
+
session.lastLogId = log.id;
|
|
164
|
+
session.lastModel = log.model ?? session.lastModel;
|
|
165
|
+
session.clientPid = log.clientPid ?? session.clientPid;
|
|
166
|
+
session.clientProjectFolder = log.clientProjectFolder ?? session.clientProjectFolder;
|
|
167
|
+
|
|
168
|
+
sessions.set(identity.id, session);
|
|
169
|
+
return session;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function rememberLogSession(log: CapturedLog): boolean {
|
|
173
|
+
const sessionId = getLogSessionId(log);
|
|
174
|
+
if (sessionId === null) return false;
|
|
175
|
+
|
|
176
|
+
const knownSessionId = knownLogSessions.get(log.id);
|
|
177
|
+
if (knownSessionId === sessionId) return false;
|
|
178
|
+
|
|
179
|
+
knownLogSessions.set(log.id, sessionId);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isFailedLog(log: CapturedLog): boolean {
|
|
184
|
+
const hasError = log.error !== null && log.error !== undefined && log.error.length > 0;
|
|
185
|
+
const hasErrorStatus = log.responseStatus !== null && log.responseStatus >= 400;
|
|
186
|
+
return hasError || hasErrorStatus;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function markSessionStarted(log: CapturedLog, sourceOverride?: SessionSource | null): void {
|
|
190
|
+
const session = upsertSession(log, sourceOverride);
|
|
191
|
+
if (session === null) return;
|
|
192
|
+
|
|
193
|
+
const isNewLog = rememberLogSession(log);
|
|
194
|
+
if (isNewLog) {
|
|
195
|
+
session.requestCount += 1;
|
|
196
|
+
}
|
|
197
|
+
session.activeRequests += 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function markSessionFinished(log: CapturedLog, sourceOverride?: SessionSource | null): void {
|
|
201
|
+
const session = upsertSession(log, sourceOverride);
|
|
202
|
+
if (session === null) return;
|
|
203
|
+
|
|
204
|
+
if (session.activeRequests > 0) {
|
|
205
|
+
session.activeRequests -= 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (completedLogIds.has(log.id) === false) {
|
|
209
|
+
completedLogIds.add(log.id);
|
|
210
|
+
session.completedRequests += 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isFailedLog(log) && failedLogIds.has(log.id) === false) {
|
|
214
|
+
failedLogIds.add(log.id);
|
|
215
|
+
session.failedRequests += 1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function markSessionTaskQueued(sessionId: string | null): void {
|
|
220
|
+
if (sessionId === null) return;
|
|
221
|
+
const session = sessions.get(sessionId);
|
|
222
|
+
if (session === undefined) return;
|
|
223
|
+
session.queuedTasks += 1;
|
|
224
|
+
session.updatedAt = new Date().toISOString();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function markSessionTaskStarted(sessionId: string | null): void {
|
|
228
|
+
if (sessionId === null) return;
|
|
229
|
+
const session = sessions.get(sessionId);
|
|
230
|
+
if (session === undefined) return;
|
|
231
|
+
if (session.queuedTasks > 0) {
|
|
232
|
+
session.queuedTasks -= 1;
|
|
233
|
+
}
|
|
234
|
+
session.runningTasks += 1;
|
|
235
|
+
session.updatedAt = new Date().toISOString();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function markSessionTaskFinished(sessionId: string | null, error: string | null): void {
|
|
239
|
+
if (sessionId === null) return;
|
|
240
|
+
const session = sessions.get(sessionId);
|
|
241
|
+
if (session === undefined) return;
|
|
242
|
+
if (session.runningTasks > 0) {
|
|
243
|
+
session.runningTasks -= 1;
|
|
244
|
+
}
|
|
245
|
+
session.lastTaskError = error;
|
|
246
|
+
session.updatedAt = new Date().toISOString();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function observeSessionLog(log: CapturedLog, sourceOverride?: SessionSource | null): void {
|
|
250
|
+
const session = upsertSession(log, sourceOverride);
|
|
251
|
+
if (session === null) return;
|
|
252
|
+
|
|
253
|
+
const isNewLog = rememberLogSession(log);
|
|
254
|
+
if (isNewLog) {
|
|
255
|
+
session.requestCount += 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (log.responseStatus !== null || log.responseText !== null) {
|
|
259
|
+
markSessionFinished(log, sourceOverride);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function clearSessionRegistry(): void {
|
|
264
|
+
sessions.clear();
|
|
265
|
+
knownLogSessions.clear();
|
|
266
|
+
completedLogIds.clear();
|
|
267
|
+
failedLogIds.clear();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function rebuildSessionRegistry(logs: Iterable<CapturedLog>): void {
|
|
271
|
+
clearSessionRegistry();
|
|
272
|
+
for (const log of logs) {
|
|
273
|
+
observeSessionLog(log);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function getSessionIds(): string[] {
|
|
278
|
+
return [...sessions.values()].map((session) => session.id);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function getSessionSnapshots(): SessionSnapshot[] {
|
|
282
|
+
return [...sessions.values()].map((session) => ({ ...session }));
|
|
283
|
+
}
|