@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,268 @@
|
|
|
1
|
+
import { readFile, writeFile, stat, readdir, mkdir } from "node:fs/promises";
|
|
2
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { resolveLogDir, logger } from "./logger";
|
|
7
|
+
|
|
8
|
+
type LogIndexEntry = {
|
|
9
|
+
id: number;
|
|
10
|
+
file: string;
|
|
11
|
+
byteOffset: number;
|
|
12
|
+
byteLength: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type LogIndex = {
|
|
16
|
+
version: number;
|
|
17
|
+
entries: Record<number, LogIndexEntry>;
|
|
18
|
+
maxId: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const INDEX_VERSION = 1;
|
|
22
|
+
const INDEX_FILE = "logs.idx";
|
|
23
|
+
|
|
24
|
+
export { resolveLogDir as getLogDir };
|
|
25
|
+
|
|
26
|
+
function getIndexPath(): string {
|
|
27
|
+
return join(resolveLogDir(), INDEX_FILE);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let cachedIndex: LogIndex | null = null;
|
|
31
|
+
|
|
32
|
+
function createEmptyIndex(): LogIndex {
|
|
33
|
+
return {
|
|
34
|
+
version: INDEX_VERSION,
|
|
35
|
+
entries: {},
|
|
36
|
+
maxId: 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isLogIndex(obj: unknown): obj is LogIndex {
|
|
41
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
|
|
42
|
+
const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
|
|
43
|
+
const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
|
|
44
|
+
const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
|
|
45
|
+
return (
|
|
46
|
+
versionDesc !== undefined &&
|
|
47
|
+
typeof versionDesc.value === "number" &&
|
|
48
|
+
entriesDesc !== undefined &&
|
|
49
|
+
typeof entriesDesc.value === "object" &&
|
|
50
|
+
entriesDesc.value !== null &&
|
|
51
|
+
maxIdDesc !== undefined &&
|
|
52
|
+
typeof maxIdDesc.value === "number"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function loadIndex(): Promise<LogIndex> {
|
|
57
|
+
if (cachedIndex !== null) return cachedIndex;
|
|
58
|
+
|
|
59
|
+
const indexPath = getIndexPath();
|
|
60
|
+
if (!existsSync(indexPath)) {
|
|
61
|
+
cachedIndex = createEmptyIndex();
|
|
62
|
+
return cachedIndex;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const content = await readFile(indexPath, "utf-8");
|
|
67
|
+
const parsed: unknown = JSON.parse(content);
|
|
68
|
+
if (isLogIndex(parsed)) {
|
|
69
|
+
cachedIndex = parsed;
|
|
70
|
+
} else {
|
|
71
|
+
cachedIndex = createEmptyIndex();
|
|
72
|
+
}
|
|
73
|
+
return cachedIndex;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.error("[logIndex] Failed to load index:", String(err));
|
|
76
|
+
cachedIndex = createEmptyIndex();
|
|
77
|
+
return cachedIndex;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function saveIndex(index: LogIndex): Promise<void> {
|
|
82
|
+
const indexPath = getIndexPath();
|
|
83
|
+
const dir = dirname(indexPath);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await mkdir(dir, { recursive: true });
|
|
87
|
+
} catch {
|
|
88
|
+
// Ignore
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await writeFile(indexPath, JSON.stringify(index), "utf-8");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
logger.error("[logIndex] Failed to save index:", String(err));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function addToIndex(
|
|
99
|
+
id: number,
|
|
100
|
+
file: string,
|
|
101
|
+
byteOffset: number,
|
|
102
|
+
byteLength: number,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const index = await loadIndex();
|
|
105
|
+
index.entries[id] = { id, file, byteOffset, byteLength };
|
|
106
|
+
if (id > index.maxId) {
|
|
107
|
+
index.maxId = id;
|
|
108
|
+
}
|
|
109
|
+
// Defer disk writes to reduce I/O - flush after a batch of updates
|
|
110
|
+
scheduleIndexFlush();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Batch writes: collect pending flushes and write once
|
|
114
|
+
let indexFlushScheduled = false;
|
|
115
|
+
let indexFlushTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
116
|
+
|
|
117
|
+
async function flushIndexAsync(): Promise<void> {
|
|
118
|
+
indexFlushScheduled = false;
|
|
119
|
+
indexFlushTimeout = null;
|
|
120
|
+
const index = await loadIndex();
|
|
121
|
+
await saveIndex(index);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function scheduleIndexFlush(): void {
|
|
125
|
+
if (indexFlushScheduled) return;
|
|
126
|
+
indexFlushScheduled = true;
|
|
127
|
+
indexFlushTimeout = setTimeout(() => {
|
|
128
|
+
void flushIndexAsync();
|
|
129
|
+
}, 1000); // Flush after 1 second of inactivity
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function flushIndex(): Promise<void> {
|
|
133
|
+
if (indexFlushTimeout !== null) {
|
|
134
|
+
clearTimeout(indexFlushTimeout);
|
|
135
|
+
indexFlushTimeout = null;
|
|
136
|
+
}
|
|
137
|
+
indexFlushScheduled = false;
|
|
138
|
+
const index = await loadIndex();
|
|
139
|
+
await saveIndex(index);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function findInIndex(id: number): Promise<LogIndexEntry | null> {
|
|
143
|
+
const index = await loadIndex();
|
|
144
|
+
return index.entries[id] ?? null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type FileIndexResult = {
|
|
148
|
+
entries: Record<number, LogIndexEntry>;
|
|
149
|
+
maxId: number;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
async function indexFile(filePath: string, file: string): Promise<FileIndexResult> {
|
|
153
|
+
const entries: Record<number, LogIndexEntry> = {};
|
|
154
|
+
let maxId = 0;
|
|
155
|
+
let byteOffset = 0;
|
|
156
|
+
|
|
157
|
+
const fileStream = createInterface({
|
|
158
|
+
input: createReadStream(filePath),
|
|
159
|
+
crlfDelay: Infinity,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
for await (const line of fileStream) {
|
|
163
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
|
|
164
|
+
if (line.trim() !== "") {
|
|
165
|
+
try {
|
|
166
|
+
const entry: unknown = JSON.parse(line);
|
|
167
|
+
if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
168
|
+
const idDesc = Object.getOwnPropertyDescriptor(entry, "id");
|
|
169
|
+
if (idDesc !== undefined && typeof idDesc.value === "number") {
|
|
170
|
+
const entryId = idDesc.value;
|
|
171
|
+
maxId = Math.max(maxId, entryId);
|
|
172
|
+
entries[entryId] = {
|
|
173
|
+
id: entryId,
|
|
174
|
+
file,
|
|
175
|
+
byteOffset,
|
|
176
|
+
byteLength: lineBytes,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// Skip malformed lines
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
byteOffset += lineBytes;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { entries, maxId };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function rebuildIndex(): Promise<LogIndex> {
|
|
191
|
+
const logDir = resolveLogDir();
|
|
192
|
+
const newIndex = createEmptyIndex();
|
|
193
|
+
|
|
194
|
+
if (!existsSync(logDir)) {
|
|
195
|
+
cachedIndex = newIndex;
|
|
196
|
+
await saveIndex(newIndex);
|
|
197
|
+
return newIndex;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const files = (await readdir(logDir)).filter((f) => f.endsWith(".jsonl")).sort();
|
|
201
|
+
const CONCURRENCY = 4;
|
|
202
|
+
|
|
203
|
+
// Process files in bounded-concurrency batches
|
|
204
|
+
for (let i = 0; i < files.length; i += CONCURRENCY) {
|
|
205
|
+
const batch = files.slice(i, i + CONCURRENCY);
|
|
206
|
+
const results = await Promise.all(batch.map((file) => indexFile(join(logDir, file), file)));
|
|
207
|
+
|
|
208
|
+
for (const result of results) {
|
|
209
|
+
Object.assign(newIndex.entries, result.entries);
|
|
210
|
+
newIndex.maxId = Math.max(newIndex.maxId, result.maxId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
cachedIndex = newIndex;
|
|
215
|
+
await saveIndex(newIndex);
|
|
216
|
+
return newIndex;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Async mutex for atomic ID generation to prevent race conditions
|
|
220
|
+
// Uses a promise queue instead of busy-waiting
|
|
221
|
+
let idGenerationPromise: Promise<void> | null = null;
|
|
222
|
+
let releaseLock: (() => void) | null = null;
|
|
223
|
+
|
|
224
|
+
async function acquireLock(): Promise<void> {
|
|
225
|
+
if (releaseLock === null) {
|
|
226
|
+
idGenerationPromise = new Promise<void>((resolve) => {
|
|
227
|
+
releaseLock = resolve;
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
// Wait for the previous lock to be released
|
|
231
|
+
await idGenerationPromise;
|
|
232
|
+
// After waiting, we need to create a new promise for the next waiter
|
|
233
|
+
idGenerationPromise = new Promise<void>((resolve) => {
|
|
234
|
+
releaseLock = resolve;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function releaseLockFn(): void {
|
|
240
|
+
if (releaseLock) {
|
|
241
|
+
const resolve = releaseLock;
|
|
242
|
+
releaseLock = null;
|
|
243
|
+
idGenerationPromise = null;
|
|
244
|
+
resolve();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function getNextLogId(): Promise<number> {
|
|
249
|
+
await acquireLock();
|
|
250
|
+
try {
|
|
251
|
+
const index = await loadIndex();
|
|
252
|
+
const nextId = index.maxId + 1;
|
|
253
|
+
index.maxId = nextId;
|
|
254
|
+
// Synchronously update the index in memory (disk write is deferred via batching)
|
|
255
|
+
cachedIndex = index;
|
|
256
|
+
return nextId;
|
|
257
|
+
} finally {
|
|
258
|
+
releaseLockFn();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function getCurrentLogFile(): string {
|
|
263
|
+
const now = new Date();
|
|
264
|
+
const yyyy = now.getUTCFullYear();
|
|
265
|
+
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
266
|
+
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
267
|
+
return `${yyyy}-${mm}-${dd}.jsonl`;
|
|
268
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { readdir, stat, unlink, appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getDataDir } from "./dataDir";
|
|
5
|
+
|
|
6
|
+
const LOG_DIR_ENV = process.env["LOG_DIR"];
|
|
7
|
+
const RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
|
|
8
|
+
const LOG_FILE_ENV = process.env["AGENT_INSPECTOR_LOG_FILE"];
|
|
9
|
+
|
|
10
|
+
let resolvedLogDir: string | null = null;
|
|
11
|
+
|
|
12
|
+
export function resolveLogDir(): string {
|
|
13
|
+
if (resolvedLogDir !== null) return resolvedLogDir;
|
|
14
|
+
|
|
15
|
+
if (LOG_DIR_ENV !== undefined) {
|
|
16
|
+
resolvedLogDir = path.isAbsolute(LOG_DIR_ENV)
|
|
17
|
+
? LOG_DIR_ENV
|
|
18
|
+
: path.join(getDataDir(), LOG_DIR_ENV);
|
|
19
|
+
} else {
|
|
20
|
+
resolvedLogDir = path.join(getDataDir(), "logs");
|
|
21
|
+
}
|
|
22
|
+
return resolvedLogDir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getLogFilePath(): string {
|
|
26
|
+
const date = new Date();
|
|
27
|
+
const yyyy = date.getUTCFullYear();
|
|
28
|
+
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
29
|
+
const dd = String(date.getUTCDate()).padStart(2, "0");
|
|
30
|
+
return path.join(resolveLogDir(), `${yyyy}-${mm}-${dd}.jsonl`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getInspectorLogPath(): string {
|
|
34
|
+
if (LOG_FILE_ENV !== undefined) {
|
|
35
|
+
return LOG_FILE_ENV;
|
|
36
|
+
}
|
|
37
|
+
return path.join(getDataDir(), "logs", "inspector.log");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function initLogger(): Promise<void> {
|
|
41
|
+
const dir = resolveLogDir();
|
|
42
|
+
const retentionMs = RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
43
|
+
const cutoff = Date.now() - retentionMs;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const entries = await readdir(dir);
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
49
|
+
const fullPath = path.join(dir, entry);
|
|
50
|
+
try {
|
|
51
|
+
const s = await stat(fullPath);
|
|
52
|
+
if (s.mtimeMs < cutoff) {
|
|
53
|
+
await unlink(fullPath);
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Skip files that can't be stat - not critical
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// Log directory initialization errors but don't fail startup
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.error("[logger] Failed to initialize log directory:", err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// File-based logger for application logs (not log entries)
|
|
67
|
+
let loggerInitialized = false;
|
|
68
|
+
|
|
69
|
+
async function writeAppLog(message: string): Promise<void> {
|
|
70
|
+
// Auto-initialize on first use if not already initialized
|
|
71
|
+
if (!loggerInitialized) {
|
|
72
|
+
loggerInitialized = true;
|
|
73
|
+
void initLogger();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const logPath = getInspectorLogPath();
|
|
77
|
+
try {
|
|
78
|
+
const logDirPath = path.dirname(logPath);
|
|
79
|
+
await mkdir(logDirPath, { recursive: true });
|
|
80
|
+
const timestamp = new Date().toISOString();
|
|
81
|
+
await appendFile(logPath, `[${timestamp}] ${message}\n`, "utf-8");
|
|
82
|
+
} catch (err) {
|
|
83
|
+
// Log to stderr since file logging failed
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error(`[logger] Failed to write to ${logPath}:`, err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Application logger - writes to inspector.log
|
|
90
|
+
export const logger = {
|
|
91
|
+
debug(message: string, ...args: unknown[]): void {
|
|
92
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
93
|
+
void writeAppLog(`[DEBUG] ${msg}`);
|
|
94
|
+
},
|
|
95
|
+
info(message: string, ...args: unknown[]): void {
|
|
96
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
97
|
+
void writeAppLog(`[INFO] ${msg}`);
|
|
98
|
+
},
|
|
99
|
+
warn(message: string, ...args: unknown[]): void {
|
|
100
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
101
|
+
void writeAppLog(`[WARN] ${msg}`);
|
|
102
|
+
},
|
|
103
|
+
error(message: string, ...args: unknown[]): void {
|
|
104
|
+
const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
|
|
105
|
+
void writeAppLog(`[ERROR] ${msg}`);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Write buffer for batching async writes
|
|
110
|
+
const MAX_BUFFER_SIZE = 1000;
|
|
111
|
+
let writeBuffer: string[] = [];
|
|
112
|
+
let writeQueue: Promise<void> = Promise.resolve();
|
|
113
|
+
|
|
114
|
+
function drainBuffer(): string {
|
|
115
|
+
const toWrite = writeBuffer.join("");
|
|
116
|
+
writeBuffer = [];
|
|
117
|
+
return toWrite;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function flushWriteBuffer(): Promise<void> {
|
|
121
|
+
let toWrite: string;
|
|
122
|
+
{
|
|
123
|
+
toWrite = drainBuffer();
|
|
124
|
+
}
|
|
125
|
+
if (toWrite.length === 0) return;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const filePath = getLogFilePath();
|
|
129
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
130
|
+
await appendFile(filePath, toWrite, "utf-8");
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// On failure, re-queue the data so it is not lost
|
|
133
|
+
writeBuffer.unshift(toWrite);
|
|
134
|
+
// eslint-disable-next-line no-console
|
|
135
|
+
console.error("[logger] Failed to flush write buffer, re-queued:", err);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function appendLogEntry(entry: Record<string, unknown>): void {
|
|
140
|
+
const line = JSON.stringify(entry) + "\n";
|
|
141
|
+
writeBuffer.push(line);
|
|
142
|
+
if (writeBuffer.length >= MAX_BUFFER_SIZE) {
|
|
143
|
+
writeQueue = writeQueue.then(() => flushWriteBuffer());
|
|
144
|
+
} else if (writeBuffer.length === 1) {
|
|
145
|
+
writeQueue = writeQueue.then(() => flushWriteBuffer());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Flush all buffered log entries to disk. Call before process exit
|
|
151
|
+
* to avoid losing unflushed data.
|
|
152
|
+
*/
|
|
153
|
+
export async function flushLogBuffer(): Promise<void> {
|
|
154
|
+
await writeQueue;
|
|
155
|
+
if (writeBuffer.length > 0) {
|
|
156
|
+
await flushWriteBuffer();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ensure buffered logs are written before the process exits
|
|
161
|
+
process.on("exit", () => {
|
|
162
|
+
if (writeBuffer.length > 0) {
|
|
163
|
+
const toWrite = drainBuffer();
|
|
164
|
+
try {
|
|
165
|
+
// Synchronous write required in 'exit' handler (async I/O not allowed)
|
|
166
|
+
const filePath = getLogFilePath();
|
|
167
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
168
|
+
writeFileSync(filePath, toWrite, "utf-8");
|
|
169
|
+
} catch {
|
|
170
|
+
// Best-effort: nothing we can do in an exit handler
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
176
|
+
process.on(signal, () => {
|
|
177
|
+
void flushLogBuffer().then(() => process.exit(0));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Chat Completions protocol guard: orphan `tool` messages.
|
|
3
|
+
*
|
|
4
|
+
* The OpenAI Chat Completions spec requires every message with
|
|
5
|
+
* `role: "tool"` to reference a `tool_call_id` that appears in a preceding
|
|
6
|
+
* `assistant` message's `tool_calls` array. When this invariant is broken
|
|
7
|
+
* (typically by a buggy client that records a tool result without the
|
|
8
|
+
* matching assistant tool_call), the upstream rejects the request with a
|
|
9
|
+
* 400 like:
|
|
10
|
+
*
|
|
11
|
+
* {"type":"error","error":{"type":"bad_request_error",
|
|
12
|
+
* "message":"invalid params, tool result's tool id(call_xxx) not found"}}
|
|
13
|
+
*
|
|
14
|
+
* This module finds such orphan tool messages and returns a rewritten body
|
|
15
|
+
* with them removed so the proxy can forward a valid request. The original
|
|
16
|
+
* body is preserved by the caller for the captured log, so the inspector UI
|
|
17
|
+
* still shows the broken request the client actually sent.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ROLE_ASSISTANT = "assistant";
|
|
21
|
+
const ROLE_TOOL = "tool";
|
|
22
|
+
|
|
23
|
+
export type OrphanToolStripResult = {
|
|
24
|
+
/** The new request body. Equal to input (by reference) when no change was made. */
|
|
25
|
+
body: string;
|
|
26
|
+
/** Number of orphan tool messages removed. */
|
|
27
|
+
removed: number;
|
|
28
|
+
/**
|
|
29
|
+
* The `tool_call_id` values of the removed messages, in document order.
|
|
30
|
+
* `null` is used when a tool message had no `tool_call_id` at all.
|
|
31
|
+
*/
|
|
32
|
+
orphanIds: Array<string | null>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
import { safeGetOwnProperty, isPlainRecord } from "../lib/objectUtils";
|
|
36
|
+
|
|
37
|
+
function isObjectWithMessages(value: unknown): value is { messages: unknown[] } {
|
|
38
|
+
if (!isPlainRecord(value)) return false;
|
|
39
|
+
if (!Object.prototype.hasOwnProperty.call(value, "messages")) return false;
|
|
40
|
+
const messages = safeGetOwnProperty(value, "messages");
|
|
41
|
+
return Array.isArray(messages);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Collects the set of tool_call ids from an assistant message's `tool_calls`
|
|
46
|
+
* array. Tolerant of malformed entries: anything without a string `id` is
|
|
47
|
+
* skipped silently (those ids cannot be referenced by a tool message anyway).
|
|
48
|
+
*/
|
|
49
|
+
function collectAssistantToolCallIds(message: unknown, into: Set<string>): void {
|
|
50
|
+
if (!isPlainRecord(message)) return;
|
|
51
|
+
const toolCalls = safeGetOwnProperty(message, "tool_calls");
|
|
52
|
+
if (!Array.isArray(toolCalls)) return;
|
|
53
|
+
for (const tc of toolCalls) {
|
|
54
|
+
if (!isPlainRecord(tc)) continue;
|
|
55
|
+
const id = safeGetOwnProperty(tc, "id");
|
|
56
|
+
if (typeof id === "string" && id.length > 0) {
|
|
57
|
+
into.add(id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the indices of `tool`-role messages whose `tool_call_id` does not
|
|
64
|
+
* appear in any *preceding* assistant message's `tool_calls`. A tool message
|
|
65
|
+
* with no `tool_call_id` at all is also considered orphan.
|
|
66
|
+
*
|
|
67
|
+
* Pure, exported for unit tests. Used by `stripOpenAIOrphanToolMessages`.
|
|
68
|
+
*/
|
|
69
|
+
export function findOrphanToolMessageIndices(messages: readonly unknown[]): {
|
|
70
|
+
indices: number[];
|
|
71
|
+
orphanIds: Array<string | null>;
|
|
72
|
+
} {
|
|
73
|
+
const knownToolCallIds = new Set<string>();
|
|
74
|
+
const indices: number[] = [];
|
|
75
|
+
const orphanIds: Array<string | null> = [];
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < messages.length; i++) {
|
|
78
|
+
const msg = messages[i];
|
|
79
|
+
if (!isPlainRecord(msg)) continue;
|
|
80
|
+
|
|
81
|
+
const role = safeGetOwnProperty(msg, "role");
|
|
82
|
+
|
|
83
|
+
if (role === ROLE_ASSISTANT) {
|
|
84
|
+
collectAssistantToolCallIds(msg, knownToolCallIds);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (role === ROLE_TOOL) {
|
|
89
|
+
const id = safeGetOwnProperty(msg, "tool_call_id");
|
|
90
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
91
|
+
indices.push(i);
|
|
92
|
+
orphanIds.push(null);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!knownToolCallIds.has(id)) {
|
|
96
|
+
indices.push(i);
|
|
97
|
+
orphanIds.push(id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { indices, orphanIds };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Inspects an OpenAI-format request body and, if its `messages` array
|
|
107
|
+
* contains one or more orphan `tool` messages (see `findOrphanToolMessageIndices`),
|
|
108
|
+
* returns a new body with those messages removed. The original string is
|
|
109
|
+
* returned unchanged (by reference equality) when no edit is needed so the
|
|
110
|
+
* caller can skip a re-serialization.
|
|
111
|
+
*
|
|
112
|
+
* Tolerant of bodies that are not valid JSON, not OpenAI-format, or that
|
|
113
|
+
* have no `messages` array — all such inputs are returned unchanged.
|
|
114
|
+
*/
|
|
115
|
+
export function stripOpenAIOrphanToolMessages(rawBody: string): OrphanToolStripResult {
|
|
116
|
+
let parsed: unknown;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(rawBody);
|
|
119
|
+
} catch {
|
|
120
|
+
return { body: rawBody, removed: 0, orphanIds: [] };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!isObjectWithMessages(parsed)) {
|
|
124
|
+
return { body: rawBody, removed: 0, orphanIds: [] };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const messages = parsed.messages;
|
|
128
|
+
const { indices, orphanIds } = findOrphanToolMessageIndices(messages);
|
|
129
|
+
|
|
130
|
+
if (indices.length === 0) {
|
|
131
|
+
return { body: rawBody, removed: 0, orphanIds: [] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const dropSet = new Set(indices);
|
|
135
|
+
const kept: unknown[] = [];
|
|
136
|
+
for (let i = 0; i < messages.length; i++) {
|
|
137
|
+
if (!dropSet.has(i)) kept.push(messages[i]);
|
|
138
|
+
}
|
|
139
|
+
parsed.messages = kept;
|
|
140
|
+
|
|
141
|
+
return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
|
|
142
|
+
}
|