@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,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildFinalizeLogResult,
|
|
3
|
+
type FinalizeLogJob,
|
|
4
|
+
type FinalizeLogResult,
|
|
5
|
+
} from "./logFinalizer";
|
|
6
|
+
|
|
7
|
+
// Child-process entry point for session finalization.
|
|
8
|
+
// Communicates with the parent via IPC (process.send / process.on("message")).
|
|
9
|
+
// Each job is { id: string; job: FinalizeLogJob }, each response is { id: string; result: FinalizeLogResult }.
|
|
10
|
+
|
|
11
|
+
process.on("message", (msg: { id: string; job: FinalizeLogJob }) => {
|
|
12
|
+
let result: FinalizeLogResult;
|
|
13
|
+
try {
|
|
14
|
+
result = buildFinalizeLogResult(msg.job);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
result = {
|
|
18
|
+
log: msg.job.log,
|
|
19
|
+
upstreamUrl: msg.job.upstreamUrl,
|
|
20
|
+
error: message,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (process.send !== undefined) {
|
|
24
|
+
process.send({ id: msg.id, result });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { exec, execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
type ClientInfo = {
|
|
9
|
+
port: number | null;
|
|
10
|
+
pid: number | null;
|
|
11
|
+
cwd: string | null;
|
|
12
|
+
projectFolder: string | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type CacheEntry = ClientInfo & {
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Cache entries: port -> { pid, cwd, projectFolder, expiresAt }
|
|
20
|
+
const cache = new Map<number, CacheEntry>();
|
|
21
|
+
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
22
|
+
const MAX_CACHE_SIZE = 200;
|
|
23
|
+
|
|
24
|
+
// Deduplicate concurrent lookups for the same port
|
|
25
|
+
const inflight = new Map<number, Promise<ClientInfo>>();
|
|
26
|
+
|
|
27
|
+
function evictCacheIfNeeded(): void {
|
|
28
|
+
while (cache.size > MAX_CACHE_SIZE) {
|
|
29
|
+
const oldestKey = cache.keys().next().value;
|
|
30
|
+
if (oldestKey === undefined) break;
|
|
31
|
+
cache.delete(oldestKey);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getFromCache(port: number): ClientInfo | null {
|
|
36
|
+
const entry = cache.get(port);
|
|
37
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
38
|
+
return { port: entry.port, pid: entry.pid, cwd: entry.cwd, projectFolder: entry.projectFolder };
|
|
39
|
+
}
|
|
40
|
+
if (entry) cache.delete(port);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setCache(port: number, info: ClientInfo): void {
|
|
45
|
+
cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
46
|
+
evictCacheIfNeeded();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the remote port from a Request's underlying socket.
|
|
51
|
+
* Works with Bun, Node.js, and Nitro's event.node.req.
|
|
52
|
+
*/
|
|
53
|
+
function extractRemotePort(request: Request): number | null {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
55
|
+
const socket = (request as any).socket;
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
57
|
+
const remotePort = (socket as any)?.remotePort;
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
59
|
+
if (remotePort !== undefined && remotePort !== null) return remotePort;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Combined lookup: PID from port, then process info from PID.
|
|
65
|
+
* On Windows, uses a single PowerShell call to get PID + command line.
|
|
66
|
+
*/
|
|
67
|
+
async function lookupClientInfo(port: number): Promise<ClientInfo> {
|
|
68
|
+
const platform = process.platform;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
if (platform === "win32") {
|
|
72
|
+
// Single PowerShell call: get PID and command line in one shot
|
|
73
|
+
const psScript = [
|
|
74
|
+
`$conn = Get-NetTCPConnection -LocalPort ${port} -State Established -ErrorAction SilentlyContinue | Select-Object -First 1`,
|
|
75
|
+
`if ($conn) {`,
|
|
76
|
+
` $pid = $conn.OwningProcess`,
|
|
77
|
+
` $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$pid" -ErrorAction SilentlyContinue`,
|
|
78
|
+
` $cmd = if ($proc) { $proc.CommandLine } else { "" }`,
|
|
79
|
+
` Write-Output "$pid|$cmd"`,
|
|
80
|
+
`}`,
|
|
81
|
+
].join("; ");
|
|
82
|
+
const { stdout } = await execFileAsync(
|
|
83
|
+
"powershell.exe",
|
|
84
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", psScript],
|
|
85
|
+
{
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
timeout: 3000,
|
|
88
|
+
maxBuffer: 64 * 1024,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
const trimmed = stdout.trim();
|
|
92
|
+
if (trimmed === "") {
|
|
93
|
+
return { port, pid: null, cwd: null, projectFolder: null };
|
|
94
|
+
}
|
|
95
|
+
const [pidStr, ...cmdParts] = trimmed.split("|");
|
|
96
|
+
const pid = parseInt(pidStr ?? "", 10);
|
|
97
|
+
if (isNaN(pid) || pid <= 0) {
|
|
98
|
+
return { port, pid: null, cwd: null, projectFolder: null };
|
|
99
|
+
}
|
|
100
|
+
const commandLine = cmdParts.join("|").trim();
|
|
101
|
+
const { cwd, projectFolder } = parseCommandLine(commandLine, "\\");
|
|
102
|
+
return { port, pid, cwd, projectFolder };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Unix: two-step lookup (PID from port, then process info from PID)
|
|
106
|
+
let pid: number | null = null;
|
|
107
|
+
|
|
108
|
+
if (platform === "darwin") {
|
|
109
|
+
const { stdout } = await execAsync(`lsof -i :${port} -sTCP:ESTABLISHED -t 2>/dev/null`, {
|
|
110
|
+
shell: "/bin/sh",
|
|
111
|
+
});
|
|
112
|
+
const parsed = parseInt(stdout.trim(), 10);
|
|
113
|
+
if (!isNaN(parsed) && parsed > 0) pid = parsed;
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
const { stdout } = await execAsync(
|
|
117
|
+
`ss -tlnp 'sport = :${port}' 2>/dev/null | grep ESTAB | awk '{print $6}' | grep -o 'pid=[0-9]*' | cut -d= -f2`,
|
|
118
|
+
{ shell: "/bin/sh" },
|
|
119
|
+
);
|
|
120
|
+
const parsed = parseInt(stdout.trim(), 10);
|
|
121
|
+
if (!isNaN(parsed) && parsed > 0) pid = parsed;
|
|
122
|
+
} catch {
|
|
123
|
+
// Fall back to netstat
|
|
124
|
+
}
|
|
125
|
+
if (pid === null) {
|
|
126
|
+
const { stdout } = await execAsync(
|
|
127
|
+
`netstat -tan 2>/dev/null | grep ':${port}' | grep ESTABLISHED | awk '{print $7}' | cut -d/ -f1`,
|
|
128
|
+
{ shell: "/bin/sh" },
|
|
129
|
+
);
|
|
130
|
+
const parsed = parseInt(stdout.trim(), 10);
|
|
131
|
+
if (!isNaN(parsed) && parsed > 0) pid = parsed;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (pid === null) {
|
|
136
|
+
return { port, pid: null, cwd: null, projectFolder: null };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { cwd, projectFolder } = await lookupProcessInfo(pid);
|
|
140
|
+
return { port, pid, cwd, projectFolder };
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.debug(`[socketTracker] Failed to lookup info for port ${port}:`, String(err));
|
|
143
|
+
return { port, pid: null, cwd: null, projectFolder: null };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseCommandLine(
|
|
148
|
+
commandLine: string,
|
|
149
|
+
sep: string,
|
|
150
|
+
): { cwd: string | null; projectFolder: string | null } {
|
|
151
|
+
if (!commandLine) return { cwd: null, projectFolder: null };
|
|
152
|
+
const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
|
|
153
|
+
if (exeMatch?.[1] !== undefined) {
|
|
154
|
+
const exePath = exeMatch[1];
|
|
155
|
+
const lastSep = exePath.lastIndexOf(sep);
|
|
156
|
+
if (lastSep > 0) {
|
|
157
|
+
const exeDir = exePath.substring(0, lastSep);
|
|
158
|
+
return { cwd: exeDir, projectFolder: exeDir.split(sep).pop() ?? null };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { cwd: null, projectFolder: null };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the working directory and command line for a process by PID.
|
|
166
|
+
*/
|
|
167
|
+
async function lookupProcessInfo(
|
|
168
|
+
pid: number,
|
|
169
|
+
): Promise<{ cwd: string | null; projectFolder: string | null }> {
|
|
170
|
+
const platform = process.platform;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
if (platform === "win32") {
|
|
174
|
+
const { stdout } = await execFileAsync(
|
|
175
|
+
"wmic.exe",
|
|
176
|
+
["process", "where", `processid=${pid}`, "get", "commandline", "/value"],
|
|
177
|
+
{
|
|
178
|
+
windowsHide: true,
|
|
179
|
+
timeout: 3000,
|
|
180
|
+
maxBuffer: 64 * 1024,
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
if (line.includes("=")) {
|
|
186
|
+
const commandLine = (line.split("=")[1] ?? "").trim();
|
|
187
|
+
if (commandLine) {
|
|
188
|
+
return parseCommandLine(commandLine, "\\");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
try {
|
|
194
|
+
const { stdout } = await execAsync(`cat /proc/${pid}/cmdline 2>/dev/null | tr '\\0' ' '`, {
|
|
195
|
+
shell: "/bin/sh",
|
|
196
|
+
});
|
|
197
|
+
const commandLine = stdout.trim();
|
|
198
|
+
if (commandLine) {
|
|
199
|
+
return parseCommandLine(commandLine, "/");
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// Fall back to ps
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (platform === "darwin") {
|
|
206
|
+
const { stdout } = await execAsync(`ps -p ${pid} -o comm= 2>/dev/null`, {
|
|
207
|
+
shell: "/bin/sh",
|
|
208
|
+
});
|
|
209
|
+
const exePath = stdout.trim();
|
|
210
|
+
if (exePath) {
|
|
211
|
+
return parseCommandLine(exePath, "/");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { cwd: null, projectFolder: null };
|
|
216
|
+
} catch {
|
|
217
|
+
return { cwd: null, projectFolder: null };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get client info (PID, CWD, project folder) from a request's source port.
|
|
223
|
+
* Deduplicates concurrent lookups for the same port and caches results.
|
|
224
|
+
*/
|
|
225
|
+
export async function getClientInfo(request: Request): Promise<ClientInfo> {
|
|
226
|
+
const port = extractRemotePort(request);
|
|
227
|
+
if (port === null) {
|
|
228
|
+
return { port: null, pid: null, cwd: null, projectFolder: null };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check cache first
|
|
232
|
+
const cached = getFromCache(port);
|
|
233
|
+
if (cached) return cached;
|
|
234
|
+
|
|
235
|
+
// Deduplicate concurrent lookups for the same port
|
|
236
|
+
const existing = inflight.get(port);
|
|
237
|
+
if (existing) return existing;
|
|
238
|
+
|
|
239
|
+
const promise = lookupClientInfo(port)
|
|
240
|
+
.then((info) => {
|
|
241
|
+
setCache(port, info);
|
|
242
|
+
inflight.delete(port);
|
|
243
|
+
return info;
|
|
244
|
+
})
|
|
245
|
+
.catch((err) => {
|
|
246
|
+
inflight.delete(port);
|
|
247
|
+
logger.debug(`[socketTracker] Lookup failed for port ${port}:`, String(err));
|
|
248
|
+
const fallback: ClientInfo = { port, pid: null, cwd: null, projectFolder: null };
|
|
249
|
+
setCache(port, fallback);
|
|
250
|
+
return fallback;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
inflight.set(port, promise);
|
|
254
|
+
return promise;
|
|
255
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { readFileSync, existsSync, createReadStream } from "node:fs";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { readdir as readdirCallback } from "node:fs/promises";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { appendLogEntry, resolveLogDir, logger } from "./logger";
|
|
8
|
+
import {
|
|
9
|
+
addToIndex,
|
|
10
|
+
findInIndex,
|
|
11
|
+
getNextLogId,
|
|
12
|
+
getCurrentLogFile,
|
|
13
|
+
saveIndex,
|
|
14
|
+
loadIndex,
|
|
15
|
+
} from "./logIndex";
|
|
16
|
+
import { writeChunks } from "./chunkStorage";
|
|
17
|
+
import type { CapturedLog } from "./schemas";
|
|
18
|
+
import { CapturedLogSchema } from "./schemas";
|
|
19
|
+
import {
|
|
20
|
+
clearSessionRegistry,
|
|
21
|
+
getLogSessionId,
|
|
22
|
+
getSessionIds,
|
|
23
|
+
markSessionFinished,
|
|
24
|
+
markSessionStarted,
|
|
25
|
+
observeSessionLog,
|
|
26
|
+
rebuildSessionRegistry,
|
|
27
|
+
resolveSessionIdentity,
|
|
28
|
+
type SessionClientInfo,
|
|
29
|
+
} from "./sessionSupervisor";
|
|
30
|
+
|
|
31
|
+
export type { CapturedLog };
|
|
32
|
+
export { getNextLogId };
|
|
33
|
+
export { getLogSessionId, getSessionSnapshots } from "./sessionSupervisor";
|
|
34
|
+
|
|
35
|
+
const MAX_MEMORY_CACHE = 100;
|
|
36
|
+
|
|
37
|
+
// Memory cache: id -> CapturedLog (recent logs only, FIFO eviction)
|
|
38
|
+
const memoryCache: Map<number, CapturedLog> = new Map();
|
|
39
|
+
|
|
40
|
+
function evictOldestIfNeeded(): void {
|
|
41
|
+
while (memoryCache.size > MAX_MEMORY_CACHE) {
|
|
42
|
+
// Map preserves insertion order; first key = oldest
|
|
43
|
+
const oldestKey = memoryCache.keys().next().value;
|
|
44
|
+
if (oldestKey === undefined) break;
|
|
45
|
+
memoryCache.delete(oldestKey);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeLogSession(log: CapturedLog): CapturedLog {
|
|
50
|
+
const sessionId = getLogSessionId(log);
|
|
51
|
+
if (sessionId === log.sessionId) return log;
|
|
52
|
+
return { ...log, sessionId };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function addToCache(log: CapturedLog): void {
|
|
56
|
+
const cachedLog = normalizeLogSession(log);
|
|
57
|
+
// If updating an existing entry, remove first to reset insertion order
|
|
58
|
+
if (memoryCache.has(cachedLog.id)) {
|
|
59
|
+
memoryCache.delete(cachedLog.id);
|
|
60
|
+
}
|
|
61
|
+
memoryCache.set(cachedLog.id, cachedLog);
|
|
62
|
+
evictOldestIfNeeded();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function removeFromCache(id: number): void {
|
|
66
|
+
memoryCache.delete(id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add a test log entry directly to the in-memory store (for dashboard display).
|
|
71
|
+
* This is used by the provider test endpoint alongside appendLogEntry (file logging).
|
|
72
|
+
*/
|
|
73
|
+
export async function addTestLogEntry(entry: Omit<CapturedLog, "id">): Promise<CapturedLog> {
|
|
74
|
+
const id = await getNextLogId();
|
|
75
|
+
// Update the index with the new maxId so subsequent calls get unique IDs
|
|
76
|
+
const index = await loadIndex();
|
|
77
|
+
if (id > index.maxId) {
|
|
78
|
+
index.maxId = id;
|
|
79
|
+
await saveIndex(index);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Persist streaming chunks to disk if present
|
|
83
|
+
let streamingChunksPath: string | null = null;
|
|
84
|
+
if (entry.streamingChunks !== undefined && entry.streamingChunks.chunks.length > 0) {
|
|
85
|
+
streamingChunksPath = writeChunks(
|
|
86
|
+
id,
|
|
87
|
+
entry.streamingChunks.chunks,
|
|
88
|
+
entry.streamingChunks.truncated,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const session = resolveSessionIdentity({
|
|
93
|
+
explicitSessionId: entry.sessionId,
|
|
94
|
+
isTest: entry.isTest,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const log: CapturedLog = {
|
|
98
|
+
id,
|
|
99
|
+
...entry,
|
|
100
|
+
sessionId: session.id,
|
|
101
|
+
streamingChunksPath,
|
|
102
|
+
};
|
|
103
|
+
addToCache(log);
|
|
104
|
+
observeSessionLog(log, session.source);
|
|
105
|
+
emitLogUpdate(log);
|
|
106
|
+
return log;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type ClientInfo = SessionClientInfo;
|
|
110
|
+
|
|
111
|
+
export async function createLog(
|
|
112
|
+
method: string,
|
|
113
|
+
path: string,
|
|
114
|
+
requestBody: string | null,
|
|
115
|
+
headers: Headers,
|
|
116
|
+
clientInfo?: ClientInfo,
|
|
117
|
+
rawHeaders?: Record<string, string>,
|
|
118
|
+
upstreamHeaders?: Record<string, string>,
|
|
119
|
+
apiFormat: "anthropic" | "openai" | "unknown" = "unknown",
|
|
120
|
+
model: string | null = null,
|
|
121
|
+
sessionId: string | null = null,
|
|
122
|
+
/** Pre-acquired log id; if omitted, `getNextLogId` is awaited internally. */
|
|
123
|
+
preAcquiredId?: number,
|
|
124
|
+
): Promise<CapturedLog> {
|
|
125
|
+
const userAgent = headers.get("user-agent");
|
|
126
|
+
const origin = headers.get("origin");
|
|
127
|
+
|
|
128
|
+
const id = preAcquiredId ?? (await getNextLogId());
|
|
129
|
+
const session = resolveSessionIdentity({
|
|
130
|
+
explicitSessionId: sessionId,
|
|
131
|
+
clientInfo,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const log: CapturedLog = {
|
|
135
|
+
id,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
method,
|
|
138
|
+
path,
|
|
139
|
+
model,
|
|
140
|
+
sessionId: session.id,
|
|
141
|
+
rawRequestBody: requestBody,
|
|
142
|
+
responseStatus: null,
|
|
143
|
+
responseText: null,
|
|
144
|
+
inputTokens: null,
|
|
145
|
+
outputTokens: null,
|
|
146
|
+
cacheCreationInputTokens: null,
|
|
147
|
+
cacheReadInputTokens: null,
|
|
148
|
+
elapsedMs: null,
|
|
149
|
+
streaming: false,
|
|
150
|
+
userAgent,
|
|
151
|
+
origin,
|
|
152
|
+
rawHeaders: rawHeaders,
|
|
153
|
+
headers: upstreamHeaders,
|
|
154
|
+
apiFormat,
|
|
155
|
+
isTest: false,
|
|
156
|
+
providerName: null,
|
|
157
|
+
clientPort: clientInfo?.port ?? null,
|
|
158
|
+
clientPid: clientInfo?.pid ?? null,
|
|
159
|
+
clientCwd: clientInfo?.cwd ?? null,
|
|
160
|
+
clientProjectFolder: clientInfo?.projectFolder ?? null,
|
|
161
|
+
streamingChunksPath: null,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Write to disk and update index
|
|
165
|
+
const logFile = getCurrentLogFile();
|
|
166
|
+
appendLogEntry(log);
|
|
167
|
+
await addToIndex(id, logFile, -1, -1); // line numbers not tracked precisely
|
|
168
|
+
|
|
169
|
+
// Add to memory cache
|
|
170
|
+
addToCache(log);
|
|
171
|
+
markSessionStarted(log, session.source);
|
|
172
|
+
emitLogUpdate(log);
|
|
173
|
+
|
|
174
|
+
return log;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function finalizeLogUpdate(log: CapturedLog): void {
|
|
178
|
+
addToCache(log);
|
|
179
|
+
markSessionFinished(log);
|
|
180
|
+
emitLogUpdate(log);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get a log by ID, checking memory cache first then disk.
|
|
185
|
+
* Uses byte-offset index for efficient single-line reads.
|
|
186
|
+
*/
|
|
187
|
+
export async function getLogById(id: number): Promise<CapturedLog | null> {
|
|
188
|
+
// Check memory cache first
|
|
189
|
+
const cached = memoryCache.get(id);
|
|
190
|
+
if (cached !== undefined) {
|
|
191
|
+
return cached;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Look up in index and load from disk
|
|
195
|
+
const entry = await findInIndex(id);
|
|
196
|
+
if (entry === null) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Load from disk using byte-offset index
|
|
201
|
+
try {
|
|
202
|
+
const filePath = join(resolveLogDir(), entry.file);
|
|
203
|
+
if (!existsSync(filePath)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Use byte offset for direct read when available
|
|
208
|
+
if (entry.byteOffset >= 0 && entry.byteLength > 0) {
|
|
209
|
+
const fh = await open(filePath, "r");
|
|
210
|
+
try {
|
|
211
|
+
const buffer = Buffer.alloc(entry.byteLength);
|
|
212
|
+
const { bytesRead } = await fh.read(buffer, 0, entry.byteLength, entry.byteOffset);
|
|
213
|
+
if (bytesRead === 0) return null;
|
|
214
|
+
const line = buffer.toString("utf-8", 0, bytesRead).trimEnd();
|
|
215
|
+
if (line === "") return null;
|
|
216
|
+
const parsed: unknown = JSON.parse(line);
|
|
217
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
218
|
+
if (result.success) {
|
|
219
|
+
const log = normalizeLogSession(result.data);
|
|
220
|
+
addToCache(log);
|
|
221
|
+
observeSessionLog(log);
|
|
222
|
+
return log;
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
await fh.close();
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Fallback: scan file for matching ID (for entries written before index rebuild)
|
|
231
|
+
const content = readFileSync(filePath, "utf-8");
|
|
232
|
+
const lines = content.split("\n");
|
|
233
|
+
|
|
234
|
+
let lastMatch: CapturedLog | null = null;
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
if (line.trim() === "") continue;
|
|
237
|
+
try {
|
|
238
|
+
const parsed: unknown = JSON.parse(line);
|
|
239
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
240
|
+
const desc = Object.getOwnPropertyDescriptor(parsed, "id");
|
|
241
|
+
if (desc !== undefined && typeof desc.value === "number" && desc.value === id) {
|
|
242
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
243
|
+
if (result.success) {
|
|
244
|
+
lastMatch = normalizeLogSession(result.data);
|
|
245
|
+
if (result.data.responseStatus !== null) {
|
|
246
|
+
addToCache(lastMatch);
|
|
247
|
+
observeSessionLog(lastMatch);
|
|
248
|
+
return lastMatch;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
// Skip malformed lines
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (lastMatch !== null) {
|
|
258
|
+
addToCache(lastMatch);
|
|
259
|
+
observeSessionLog(lastMatch);
|
|
260
|
+
return lastMatch;
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
logger.error("[store] Failed to read log from disk:", String(err));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function getFilteredLogs(sessionId?: string, model?: string): CapturedLog[] {
|
|
270
|
+
// Cache maintains insertion order (sorted by ID since logs are added in ID order)
|
|
271
|
+
// Use spread instead of Array.from for slightly better performance
|
|
272
|
+
return [...memoryCache.values()].filter((l) => {
|
|
273
|
+
if (sessionId !== undefined && getLogSessionId(l) !== sessionId) return false;
|
|
274
|
+
if (model !== undefined && l.model !== model) return false;
|
|
275
|
+
return true;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function getSessions(): string[] {
|
|
280
|
+
return getSessionIds();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function getModels(): string[] {
|
|
284
|
+
const set = new Set<string>();
|
|
285
|
+
for (const l of memoryCache.values()) {
|
|
286
|
+
if (l.model !== null && l.model !== "") set.add(l.model);
|
|
287
|
+
}
|
|
288
|
+
return [...set];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Load recent completed log entries from all log files into the memory cache.
|
|
293
|
+
* Called on server startup so the frontend shows existing logs.
|
|
294
|
+
*
|
|
295
|
+
* Strategy: stream files forward, keep the LAST completed entry per ID,
|
|
296
|
+
* then only retain the newest MAX_MEMORY_CACHE entries in cache.
|
|
297
|
+
*/
|
|
298
|
+
export async function loadLogsIntoMemory(): Promise<void> {
|
|
299
|
+
const logDir = resolveLogDir();
|
|
300
|
+
if (!existsSync(logDir)) return;
|
|
301
|
+
|
|
302
|
+
// lastCompletedById: id -> CapturedLog (overwrite on each occurrence)
|
|
303
|
+
const lastCompletedById = new Map<number, CapturedLog>();
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const entries = await readdirCallback(logDir);
|
|
307
|
+
const filesWithExt = entries.filter((f: string) => f.endsWith(".jsonl")).sort();
|
|
308
|
+
for (const file of filesWithExt) {
|
|
309
|
+
const filePath = join(logDir, file);
|
|
310
|
+
await loadLogFile(filePath, lastCompletedById);
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
logger.error("[store] Failed to read log directory:", String(err));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Sort by id descending, keep only MAX_MEMORY_CACHE newest completed entries
|
|
317
|
+
const sorted = [...lastCompletedById.values()].sort((a, b) => b.id - a.id);
|
|
318
|
+
for (let i = 0; i < Math.min(sorted.length, MAX_MEMORY_CACHE); i++) {
|
|
319
|
+
const entry = sorted[i];
|
|
320
|
+
if (entry !== undefined) addToCache(entry);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
rebuildSessionRegistry(memoryCache.values());
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function loadLogFile(
|
|
327
|
+
filePath: string,
|
|
328
|
+
lastCompletedById: Map<number, CapturedLog>,
|
|
329
|
+
): Promise<void> {
|
|
330
|
+
if (!existsSync(filePath)) return;
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const fileStream = createInterface({
|
|
334
|
+
input: createReadStream(filePath),
|
|
335
|
+
crlfDelay: Infinity,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
for await (const line of fileStream) {
|
|
339
|
+
if (line.trim() === "") continue;
|
|
340
|
+
try {
|
|
341
|
+
const parsed: unknown = JSON.parse(line);
|
|
342
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
343
|
+
const desc = Object.getOwnPropertyDescriptor(parsed, "id");
|
|
344
|
+
if (desc !== undefined && typeof desc.value === "number") {
|
|
345
|
+
const statusDesc = Object.getOwnPropertyDescriptor(parsed, "responseStatus");
|
|
346
|
+
if (statusDesc !== undefined && statusDesc.value !== null) {
|
|
347
|
+
const result = CapturedLogSchema.safeParse(parsed);
|
|
348
|
+
if (result.success) {
|
|
349
|
+
lastCompletedById.set(desc.value, result.data);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
// Skip malformed lines
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
logger.error("[store] Failed to load logs into memory:", String(err));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function clearAllLogs(): { cleared: number } {
|
|
364
|
+
const count = memoryCache.size;
|
|
365
|
+
memoryCache.clear();
|
|
366
|
+
clearSessionRegistry();
|
|
367
|
+
return { cleared: count };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Remove a specific set of log IDs from the in-memory cache. Returns the
|
|
372
|
+
* number of logs actually removed. IDs not present in the cache are ignored.
|
|
373
|
+
* This is used by the per-conversation-group "Clear" button in the UI.
|
|
374
|
+
*/
|
|
375
|
+
export function clearLogsByIds(ids: readonly number[]): { cleared: number } {
|
|
376
|
+
const unique = new Set<number>();
|
|
377
|
+
for (const id of ids) {
|
|
378
|
+
if (memoryCache.has(id)) unique.add(id);
|
|
379
|
+
}
|
|
380
|
+
for (const id of unique) {
|
|
381
|
+
removeFromCache(id);
|
|
382
|
+
}
|
|
383
|
+
rebuildSessionRegistry(memoryCache.values());
|
|
384
|
+
return { cleared: unique.size };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// SSE event system for real-time log updates
|
|
388
|
+
type LogUpdateHandler = (log: CapturedLog) => void;
|
|
389
|
+
const sseHandlers: Set<LogUpdateHandler> = new Set();
|
|
390
|
+
|
|
391
|
+
export function onLogUpdate(handler: LogUpdateHandler): () => void {
|
|
392
|
+
sseHandlers.add(handler);
|
|
393
|
+
return () => {
|
|
394
|
+
sseHandlers.delete(handler);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function emitLogUpdate(log: CapturedLog): void {
|
|
399
|
+
const failedHandlers: LogUpdateHandler[] = [];
|
|
400
|
+
for (const handler of sseHandlers) {
|
|
401
|
+
try {
|
|
402
|
+
handler(log);
|
|
403
|
+
} catch {
|
|
404
|
+
// Collect failed handlers to remove after iteration
|
|
405
|
+
failedHandlers.push(handler);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Remove failed handlers after iteration to avoid Set mutation during for...of
|
|
409
|
+
for (const handler of failedHandlers) {
|
|
410
|
+
sseHandlers.delete(handler);
|
|
411
|
+
}
|
|
412
|
+
}
|