@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,90 @@
|
|
|
1
|
+
import type { ProviderConfig } from "./providers";
|
|
2
|
+
import {
|
|
3
|
+
AUTH_HEADER_X_API_KEY,
|
|
4
|
+
DEFAULT_OPENAI_UPSTREAM,
|
|
5
|
+
DEFAULT_UPSTREAM,
|
|
6
|
+
HEADER_AUTHORIZATION,
|
|
7
|
+
HEADER_HOST,
|
|
8
|
+
HEADER_X_API_KEY,
|
|
9
|
+
PATH_CHAT_COMPLETIONS,
|
|
10
|
+
PATH_V1_CHAT_COMPLETIONS,
|
|
11
|
+
} from "./constants";
|
|
12
|
+
|
|
13
|
+
export type ApiRoute = {
|
|
14
|
+
apiPath: string;
|
|
15
|
+
endpointPath: string;
|
|
16
|
+
isChatCompletions: boolean;
|
|
17
|
+
normalizedPath: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Describes the protocol implied by the client-facing path.
|
|
22
|
+
*
|
|
23
|
+
* The path is the source of truth for request/response format. ProviderConfig.format
|
|
24
|
+
* is only a fallback for old provider records that do not have a format-specific URL.
|
|
25
|
+
*/
|
|
26
|
+
export function describeApiRoute(apiPath: string): ApiRoute {
|
|
27
|
+
const endpointPath = apiPath.split("?")[0] ?? "";
|
|
28
|
+
const isChatCompletions =
|
|
29
|
+
endpointPath === PATH_CHAT_COMPLETIONS || endpointPath === PATH_V1_CHAT_COMPLETIONS;
|
|
30
|
+
const normalizedPath =
|
|
31
|
+
isChatCompletions && !apiPath.startsWith("/v1/") ? `/v1${apiPath}` : apiPath;
|
|
32
|
+
|
|
33
|
+
return { apiPath, endpointPath, isChatCompletions, normalizedPath };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getProxyApiPath(url: URL): string {
|
|
37
|
+
return url.pathname.replace(/^\/proxy/, "") + url.search;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function selectUpstreamBase(route: ApiRoute, provider: ProviderConfig | null): string {
|
|
41
|
+
if (provider === null) {
|
|
42
|
+
return route.isChatCompletions ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const formatSpecificUrl = route.isChatCompletions
|
|
46
|
+
? provider.openaiBaseUrl
|
|
47
|
+
: provider.anthropicBaseUrl;
|
|
48
|
+
if (formatSpecificUrl !== undefined && formatSpecificUrl !== "") {
|
|
49
|
+
return formatSpecificUrl;
|
|
50
|
+
}
|
|
51
|
+
if (provider.baseUrl !== undefined && provider.baseUrl !== "") {
|
|
52
|
+
return provider.baseUrl;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Legacy records may only contain format. Keep this fallback for compatibility,
|
|
56
|
+
// but never use it to infer the response parser; that decision comes from the path.
|
|
57
|
+
return provider.format === "openai" ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string): string {
|
|
61
|
+
const base = upstreamBase.endsWith("/") ? upstreamBase.slice(0, -1) : upstreamBase;
|
|
62
|
+
|
|
63
|
+
// Many OpenAI-compatible base URLs already include /v1. Avoid producing /v1/v1.
|
|
64
|
+
if (base.endsWith("/v1") && normalizedPath.startsWith("/v1/")) {
|
|
65
|
+
return base + normalizedPath.slice(3);
|
|
66
|
+
}
|
|
67
|
+
return base + normalizedPath;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function setUpstreamHost(headers: Headers, upstreamBase: string): void {
|
|
71
|
+
try {
|
|
72
|
+
headers.set(HEADER_HOST, new URL(upstreamBase).host);
|
|
73
|
+
} catch {
|
|
74
|
+
// Invalid provider URLs are rejected by the UI. Retain the historical default
|
|
75
|
+
// for imported or manually edited config that bypassed that validation.
|
|
76
|
+
headers.set(HEADER_HOST, "api.anthropic.com");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function injectProviderAuth(headers: Headers, provider: ProviderConfig | null): void {
|
|
81
|
+
if (provider === null) return;
|
|
82
|
+
|
|
83
|
+
const apiKey = provider.apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
84
|
+
if (provider.authHeader === AUTH_HEADER_X_API_KEY) {
|
|
85
|
+
headers.set(HEADER_X_API_KEY, apiKey);
|
|
86
|
+
headers.delete(HEADER_AUTHORIZATION);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
headers.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
|
|
90
|
+
}
|
package/src/router.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRouter } from "@tanstack/react-router";
|
|
2
|
+
import { routeTree } from "./routeTree.gen";
|
|
3
|
+
|
|
4
|
+
export function getRouter() {
|
|
5
|
+
const router = createRouter({
|
|
6
|
+
routeTree,
|
|
7
|
+
scrollRestoration: false,
|
|
8
|
+
});
|
|
9
|
+
return router;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module "@tanstack/react-router" {
|
|
13
|
+
interface Register {
|
|
14
|
+
router: ReturnType<typeof getRouter>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { SWRConfig } from "swr";
|
|
5
|
+
import faviconSvg from "../assets/favicon.svg?url";
|
|
6
|
+
import appCss from "../index.css?url";
|
|
7
|
+
|
|
8
|
+
export const Route = createRootRoute({
|
|
9
|
+
head: () => ({
|
|
10
|
+
meta: [
|
|
11
|
+
{ charSet: "utf-8" },
|
|
12
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
13
|
+
{ title: "Agent Inspector" },
|
|
14
|
+
],
|
|
15
|
+
links: [
|
|
16
|
+
{ rel: "stylesheet", href: appCss },
|
|
17
|
+
{ rel: "icon", type: "image/svg+xml", href: faviconSvg },
|
|
18
|
+
],
|
|
19
|
+
}),
|
|
20
|
+
component: RootComponent,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function RootComponent() {
|
|
24
|
+
return (
|
|
25
|
+
<RootDocument>
|
|
26
|
+
<Outlet />
|
|
27
|
+
</RootDocument>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
32
|
+
return (
|
|
33
|
+
<html lang="en" className="dark">
|
|
34
|
+
<head>
|
|
35
|
+
<HeadContent />
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<SWRConfig value={{ revalidateOnFocus: false, revalidateIfStale: false }}>
|
|
39
|
+
{children}
|
|
40
|
+
</SWRConfig>
|
|
41
|
+
<Scripts />
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { store } from "../../proxy/providers";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/config/paths")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
GET: () => {
|
|
8
|
+
return Response.json({
|
|
9
|
+
providerConfig: store.path,
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { MAX_SLOW_RESPONSE_THRESHOLD_SECONDS } from "../../lib/runtimeConfig";
|
|
4
|
+
import { getConfig, setConfig, RuntimeConfigSchema } from "../../proxy/config";
|
|
5
|
+
|
|
6
|
+
// Partial schema for PATCH: at least one known field must be present.
|
|
7
|
+
const RuntimeConfigPatchSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
stripClaudeCodeBillingHeader: z.boolean().optional(),
|
|
10
|
+
hasSeenOnboarding: z.boolean().optional(),
|
|
11
|
+
slowResponseThresholdSeconds: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(0)
|
|
15
|
+
.max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS)
|
|
16
|
+
.optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict()
|
|
19
|
+
.refine((v) => Object.keys(v).length > 0, {
|
|
20
|
+
message: "At least one field must be provided",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const Route = createFileRoute("/api/config")({
|
|
24
|
+
server: {
|
|
25
|
+
handlers: {
|
|
26
|
+
GET: () => {
|
|
27
|
+
return Response.json(getConfig());
|
|
28
|
+
},
|
|
29
|
+
PATCH: async ({ request }: { request: Request }) => {
|
|
30
|
+
let body: unknown;
|
|
31
|
+
try {
|
|
32
|
+
body = await request.json();
|
|
33
|
+
} catch {
|
|
34
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = RuntimeConfigPatchSchema.safeParse(body);
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
return Response.json(
|
|
40
|
+
{
|
|
41
|
+
error: "Invalid config payload",
|
|
42
|
+
issues: result.error.issues,
|
|
43
|
+
},
|
|
44
|
+
{ status: 400 },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const updated = setConfig(result.data);
|
|
49
|
+
return Response.json(updated satisfies z.infer<typeof RuntimeConfigSchema>);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { logger } from "../../proxy/logger";
|
|
3
|
+
|
|
4
|
+
// Trigger logger auto-initialization when this module loads
|
|
5
|
+
logger.debug("Health endpoint loaded");
|
|
6
|
+
|
|
7
|
+
export const Route = createFileRoute("/api/health")({
|
|
8
|
+
server: {
|
|
9
|
+
handlers: {
|
|
10
|
+
GET: () => {
|
|
11
|
+
return Response.json({ status: "ok" });
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import {
|
|
3
|
+
getCandidate,
|
|
4
|
+
markCandidateFailed,
|
|
5
|
+
markCandidatePromoted,
|
|
6
|
+
} from "../../knowledge/candidateStore";
|
|
7
|
+
import { promoteToOpenClaw } from "../../knowledge/openclawClient";
|
|
8
|
+
|
|
9
|
+
export const Route = createFileRoute("/api/knowledge/candidates/$candidateId/promote")({
|
|
10
|
+
server: {
|
|
11
|
+
handlers: {
|
|
12
|
+
POST: async ({ params }: { params: { candidateId: string } }) => {
|
|
13
|
+
const candidate = getCandidate(params.candidateId);
|
|
14
|
+
if (candidate === null) {
|
|
15
|
+
return Response.json({ error: "Knowledge candidate not found" }, { status: 404 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = await promoteToOpenClaw(candidate);
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
const updated = markCandidateFailed(candidate.id, result.error);
|
|
21
|
+
return Response.json(
|
|
22
|
+
{ error: result.error, candidate: updated ?? candidate },
|
|
23
|
+
{ status: 400 },
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const updated = markCandidatePromoted(candidate.id, result.memoryId);
|
|
28
|
+
return Response.json({ candidate: updated ?? candidate });
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { listCandidates } from "../../knowledge/candidateStore";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/knowledge/candidates")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
GET: () => Response.json({ candidates: listCandidates() }),
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { searchOpenClaw } from "../../knowledge/openclawClient";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/knowledge/project-context")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
GET: async ({ request }: { request: Request }) => {
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
const project = url.searchParams.get("project") ?? "";
|
|
10
|
+
if (project === "") {
|
|
11
|
+
return Response.json({ error: "Missing project search parameter" }, { status: 400 });
|
|
12
|
+
}
|
|
13
|
+
const response = await searchOpenClaw(`project context ${project}`, project);
|
|
14
|
+
return Response.json({ project, ...response });
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { searchOpenClaw } from "../../knowledge/openclawClient";
|
|
4
|
+
|
|
5
|
+
const SearchBodySchema = z.object({
|
|
6
|
+
query: z.string().min(1),
|
|
7
|
+
project: z.string().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const Route = createFileRoute("/api/knowledge/search")({
|
|
11
|
+
server: {
|
|
12
|
+
handlers: {
|
|
13
|
+
GET: async ({ request }: { request: Request }) => {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const query = url.searchParams.get("q") ?? "";
|
|
16
|
+
const project = url.searchParams.get("project") ?? undefined;
|
|
17
|
+
if (query === "") {
|
|
18
|
+
return Response.json({ error: "Missing q search parameter" }, { status: 400 });
|
|
19
|
+
}
|
|
20
|
+
return Response.json(await searchOpenClaw(query, project));
|
|
21
|
+
},
|
|
22
|
+
POST: async ({ request }: { request: Request }) => {
|
|
23
|
+
const parsed = SearchBodySchema.safeParse(await request.json());
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
return Response.json(await searchOpenClaw(parsed.data.query, parsed.data.project));
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { getFilteredLogs } from "../../proxy/store";
|
|
3
|
+
import { distillSessionCandidates } from "../../knowledge/distiller";
|
|
4
|
+
import { saveCandidates } from "../../knowledge/candidateStore";
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute("/api/knowledge/sessions/$sessionId/candidates")({
|
|
7
|
+
server: {
|
|
8
|
+
handlers: {
|
|
9
|
+
POST: ({ params }: { params: { sessionId: string } }) => {
|
|
10
|
+
const logs = getFilteredLogs(params.sessionId);
|
|
11
|
+
const candidates = saveCandidates(distillSessionCandidates(params.sessionId, logs));
|
|
12
|
+
return Response.json({ candidates });
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { getLogById } from "../../proxy/store";
|
|
3
|
+
import { readChunks } from "../../proxy/chunkStorage";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/api/logs/$id/chunks")({
|
|
6
|
+
server: {
|
|
7
|
+
handlers: {
|
|
8
|
+
GET: async ({ params }: { params: { id: string } }) => {
|
|
9
|
+
const id = Number(params.id);
|
|
10
|
+
if (isNaN(id)) {
|
|
11
|
+
return Response.json({ error: "Invalid log ID" }, { status: 400 });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const log = await getLogById(id);
|
|
15
|
+
if (log === null) {
|
|
16
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Try disk file first (persistent chunks from real proxy streaming)
|
|
20
|
+
if (log.streamingChunksPath !== null && log.streamingChunksPath !== undefined) {
|
|
21
|
+
const chunksData = readChunks(log.streamingChunksPath);
|
|
22
|
+
if (chunksData !== null) {
|
|
23
|
+
return Response.json(chunksData);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Fall back to in-memory streamingChunks (e.g., from provider test)
|
|
28
|
+
if (log.streamingChunks !== undefined && log.streamingChunks.chunks.length > 0) {
|
|
29
|
+
return Response.json(log.streamingChunks);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Response.json({ error: "Chunks not found" }, { status: 404 });
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getLogById } from "../../proxy/store";
|
|
4
|
+
import { findProviderByModel } from "../../proxy/providers";
|
|
5
|
+
import { registry } from "../../proxy/formats";
|
|
6
|
+
import { formatForPath } from "../../proxy/formats";
|
|
7
|
+
import { extractModelFromBody } from "../../proxy/schemas";
|
|
8
|
+
import {
|
|
9
|
+
PROXY_IDENTITY,
|
|
10
|
+
PRESERVE_HEADERS,
|
|
11
|
+
HEADER_CONTENT_TYPE,
|
|
12
|
+
HEADER_USER_AGENT,
|
|
13
|
+
HEADER_X_PROXY_IDENTITY,
|
|
14
|
+
CONTENT_TYPE_EVENT_STREAM,
|
|
15
|
+
STATUS_FORBIDDEN,
|
|
16
|
+
} from "../../proxy/constants";
|
|
17
|
+
import {
|
|
18
|
+
buildUpstreamUrl,
|
|
19
|
+
describeApiRoute,
|
|
20
|
+
injectProviderAuth,
|
|
21
|
+
selectUpstreamBase,
|
|
22
|
+
setUpstreamHost,
|
|
23
|
+
} from "../../proxy/upstream";
|
|
24
|
+
|
|
25
|
+
type ReplayRequest = {
|
|
26
|
+
modifiedBody: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const ReplayRequestSchema = z.object({
|
|
30
|
+
modifiedBody: z.string(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type ReplayResponse = {
|
|
34
|
+
success: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
responseStatus?: number;
|
|
37
|
+
responseText?: string;
|
|
38
|
+
inputTokens?: number;
|
|
39
|
+
outputTokens?: number;
|
|
40
|
+
elapsedMs?: number;
|
|
41
|
+
streaming?: boolean;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Route = createFileRoute("/api/logs/$id/replay")({
|
|
45
|
+
server: {
|
|
46
|
+
handlers: {
|
|
47
|
+
POST: async ({ params, request }: { params: { id: string }; request: Request }) => {
|
|
48
|
+
const id = Number(params.id);
|
|
49
|
+
if (isNaN(id)) {
|
|
50
|
+
return Response.json({ error: "Invalid log ID" }, { status: 400 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let replayRequest: ReplayRequest;
|
|
54
|
+
try {
|
|
55
|
+
const raw: unknown = await request.json();
|
|
56
|
+
const parsed = ReplayRequestSchema.safeParse(raw);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
return Response.json({ error: "Invalid request body" }, { status: 400 });
|
|
59
|
+
}
|
|
60
|
+
replayRequest = parsed.data;
|
|
61
|
+
} catch {
|
|
62
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { modifiedBody } = replayRequest;
|
|
66
|
+
|
|
67
|
+
// Get original log for context
|
|
68
|
+
const log = await getLogById(id);
|
|
69
|
+
if (log === null) {
|
|
70
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Extract model from modified body
|
|
74
|
+
const model = extractModelFromBody(modifiedBody);
|
|
75
|
+
if (model === null) {
|
|
76
|
+
return Response.json(
|
|
77
|
+
{ error: "Could not extract model from request body" },
|
|
78
|
+
{ status: 400 },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Find provider
|
|
83
|
+
const matchedProviderConfig = findProviderByModel(model);
|
|
84
|
+
const provider = registry.findProvider(model);
|
|
85
|
+
if (provider === null) {
|
|
86
|
+
return Response.json(
|
|
87
|
+
{ error: "No provider found for model" },
|
|
88
|
+
{ status: STATUS_FORBIDDEN },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Determine API path from original log
|
|
93
|
+
const route = describeApiRoute(log.path);
|
|
94
|
+
|
|
95
|
+
// Get format handler
|
|
96
|
+
const formatHandler = formatForPath(route.apiPath);
|
|
97
|
+
if (formatHandler === null) {
|
|
98
|
+
return Response.json({ error: "Unsupported path" }, { status: STATUS_FORBIDDEN });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build upstream URL
|
|
102
|
+
const upstreamBase = selectUpstreamBase(route, matchedProviderConfig);
|
|
103
|
+
const upstreamUrl = buildUpstreamUrl(upstreamBase, route.normalizedPath);
|
|
104
|
+
|
|
105
|
+
// Build headers
|
|
106
|
+
const headers = new Headers();
|
|
107
|
+
headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
|
|
108
|
+
headers.set(HEADER_X_PROXY_IDENTITY, PROXY_IDENTITY);
|
|
109
|
+
headers.set(HEADER_CONTENT_TYPE, "application/json");
|
|
110
|
+
|
|
111
|
+
for (const name of PRESERVE_HEADERS) {
|
|
112
|
+
const value = log.rawHeaders?.[name.toLowerCase()];
|
|
113
|
+
if (value !== undefined) {
|
|
114
|
+
headers.set(name, value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
setUpstreamHost(headers, upstreamBase);
|
|
118
|
+
injectProviderAuth(headers, matchedProviderConfig);
|
|
119
|
+
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
|
|
122
|
+
// Send request
|
|
123
|
+
let upstreamRes: Response;
|
|
124
|
+
try {
|
|
125
|
+
upstreamRes = await fetch(upstreamUrl, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers,
|
|
128
|
+
body: modifiedBody,
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return Response.json({
|
|
132
|
+
success: false,
|
|
133
|
+
error: String(err),
|
|
134
|
+
} satisfies ReplayResponse);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const elapsedMs = Date.now() - startTime;
|
|
138
|
+
const isStream =
|
|
139
|
+
upstreamRes.headers.get(HEADER_CONTENT_TYPE)?.includes(CONTENT_TYPE_EVENT_STREAM) ??
|
|
140
|
+
false;
|
|
141
|
+
|
|
142
|
+
if (isStream) {
|
|
143
|
+
// Accumulate streaming response
|
|
144
|
+
const chunks: string[] = [];
|
|
145
|
+
const decoder = new TextDecoder();
|
|
146
|
+
const reader = upstreamRes.body?.getReader();
|
|
147
|
+
if (reader) {
|
|
148
|
+
try {
|
|
149
|
+
while (true) {
|
|
150
|
+
const { done, value } = await reader.read();
|
|
151
|
+
if (done) break;
|
|
152
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// Stream read error
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const fullResponse = chunks.join("");
|
|
159
|
+
|
|
160
|
+
// Create a mock log for streaming extraction
|
|
161
|
+
const mockLog: typeof log = { ...log };
|
|
162
|
+
const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
|
|
163
|
+
const tokens = formatHandler.extractTokens(responseText);
|
|
164
|
+
|
|
165
|
+
return Response.json({
|
|
166
|
+
success: true,
|
|
167
|
+
responseStatus: upstreamRes.status,
|
|
168
|
+
responseText,
|
|
169
|
+
inputTokens: tokens.inputTokens ?? undefined,
|
|
170
|
+
outputTokens: tokens.outputTokens ?? undefined,
|
|
171
|
+
elapsedMs,
|
|
172
|
+
streaming: true,
|
|
173
|
+
} satisfies ReplayResponse);
|
|
174
|
+
} else {
|
|
175
|
+
const responseText = await upstreamRes.text();
|
|
176
|
+
const tokens = formatHandler.extractTokens(responseText);
|
|
177
|
+
|
|
178
|
+
return Response.json({
|
|
179
|
+
success: true,
|
|
180
|
+
responseStatus: upstreamRes.status,
|
|
181
|
+
responseText,
|
|
182
|
+
inputTokens: tokens.inputTokens ?? undefined,
|
|
183
|
+
outputTokens: tokens.outputTokens ?? undefined,
|
|
184
|
+
elapsedMs,
|
|
185
|
+
streaming: false,
|
|
186
|
+
} satisfies ReplayResponse);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { getLogById } from "../../proxy/store";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/logs/$id")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
GET: async ({ params }: { params: { id: string } }) => {
|
|
8
|
+
const id = Number(params.id);
|
|
9
|
+
if (isNaN(id)) {
|
|
10
|
+
return Response.json({ error: "Invalid ID" }, { status: 400 });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const log = await getLogById(id);
|
|
14
|
+
if (log === null) {
|
|
15
|
+
return Response.json({ error: "Log not found" }, { status: 404 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Response.json(log);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { onLogUpdate, getFilteredLogs, getLogSessionId } from "../../proxy/store";
|
|
3
|
+
import { type CapturedLog } from "../../proxy/schemas";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/api/logs/stream")({
|
|
6
|
+
server: {
|
|
7
|
+
handlers: {
|
|
8
|
+
GET: ({ request }: { request: Request }) => {
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const sessionId = url.searchParams.get("sessionId") ?? undefined;
|
|
11
|
+
const model = url.searchParams.get("model") ?? undefined;
|
|
12
|
+
|
|
13
|
+
let controllerRef: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
14
|
+
let cleanedUp = false;
|
|
15
|
+
|
|
16
|
+
const unsubscribe = onLogUpdate((log: CapturedLog) => {
|
|
17
|
+
if (controllerRef === null) return;
|
|
18
|
+
// Filter by session/model if specified
|
|
19
|
+
if (sessionId !== undefined && getLogSessionId(log) !== sessionId) return;
|
|
20
|
+
if (model !== undefined && log.model !== model) return;
|
|
21
|
+
try {
|
|
22
|
+
const data = `data: ${JSON.stringify({ type: "update", log })}\n\n`;
|
|
23
|
+
controllerRef.enqueue(new TextEncoder().encode(data));
|
|
24
|
+
} catch {
|
|
25
|
+
cleanup();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const cleanup = (): void => {
|
|
30
|
+
if (cleanedUp) return;
|
|
31
|
+
cleanedUp = true;
|
|
32
|
+
clearInterval(heartbeat);
|
|
33
|
+
unsubscribe();
|
|
34
|
+
controllerRef = null;
|
|
35
|
+
request.signal.removeEventListener("abort", cleanup);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Send heartbeat comment every 30s to keep connection alive
|
|
39
|
+
const heartbeat = setInterval(() => {
|
|
40
|
+
if (controllerRef !== null) {
|
|
41
|
+
try {
|
|
42
|
+
controllerRef.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
|
|
43
|
+
} catch {
|
|
44
|
+
cleanup();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, 30000);
|
|
48
|
+
|
|
49
|
+
request.signal.addEventListener("abort", cleanup, { once: true });
|
|
50
|
+
|
|
51
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
52
|
+
start(controller) {
|
|
53
|
+
controllerRef = controller;
|
|
54
|
+
// Send initial batch of logs immediately
|
|
55
|
+
const logs = getFilteredLogs(sessionId, model);
|
|
56
|
+
const initData = `data: ${JSON.stringify({ type: "init", logs })}\n\n`;
|
|
57
|
+
controller.enqueue(new TextEncoder().encode(initData));
|
|
58
|
+
},
|
|
59
|
+
cancel() {
|
|
60
|
+
cleanup();
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return new Response(stream, {
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "text/event-stream",
|
|
67
|
+
"Cache-Control": "no-cache",
|
|
68
|
+
Connection: "keep-alive",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|