@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,613 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { logger } from "./logger";
|
|
7
|
+
import { getDataDir, hasExplicitDataDir } from "./dataDir";
|
|
8
|
+
import { ProviderConfigSchema, type ProviderConfig } from "../lib/providerContract";
|
|
9
|
+
|
|
10
|
+
export { ProviderConfigSchema };
|
|
11
|
+
export type { ProviderConfig };
|
|
12
|
+
|
|
13
|
+
const ProvidersStoreSchema = z.object({
|
|
14
|
+
providers: z.array(ProviderConfigSchema),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
|
|
18
|
+
|
|
19
|
+
const LegacyProviderConfigSchema = z.object({
|
|
20
|
+
id: z.string().optional(),
|
|
21
|
+
name: z.string().optional(),
|
|
22
|
+
type: z.enum(["anthropic", "openai"]).optional(),
|
|
23
|
+
format: z.enum(["anthropic", "openai"]).optional(),
|
|
24
|
+
apiKey: z.string().optional(),
|
|
25
|
+
model: z.string().optional(),
|
|
26
|
+
models: z.array(z.string()).optional(),
|
|
27
|
+
baseUrl: z.string().optional(),
|
|
28
|
+
anthropicBaseUrl: z.string().optional(),
|
|
29
|
+
openaiBaseUrl: z.string().optional(),
|
|
30
|
+
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
31
|
+
apiDocsUrl: z.string().optional(),
|
|
32
|
+
source: z.enum(["company", "personal"]).optional(),
|
|
33
|
+
createdAt: z.string().optional(),
|
|
34
|
+
updatedAt: z.string().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type LegacyProviderConfig = z.infer<typeof LegacyProviderConfigSchema>;
|
|
38
|
+
|
|
39
|
+
const LegacyProviderStoreSchema = z.object({
|
|
40
|
+
providers: z.array(LegacyProviderConfigSchema),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function legacyModels(provider: LegacyProviderConfig): string[] {
|
|
44
|
+
const modelList = provider.models?.map((model) => model.trim()).filter((model) => model !== "");
|
|
45
|
+
if (modelList !== undefined && modelList.length > 0) return modelList;
|
|
46
|
+
const model = provider.model?.trim();
|
|
47
|
+
if (model !== undefined && model !== "") return [model];
|
|
48
|
+
return [""];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function migrateLegacyProvider(
|
|
52
|
+
provider: LegacyProviderConfig,
|
|
53
|
+
index: number,
|
|
54
|
+
now: string,
|
|
55
|
+
): ProviderConfig | null {
|
|
56
|
+
const apiKey = normalizeApiKey(provider.apiKey ?? "");
|
|
57
|
+
if (apiKey === "") return null;
|
|
58
|
+
|
|
59
|
+
const format = provider.format ?? provider.type;
|
|
60
|
+
const fallbackName =
|
|
61
|
+
format === "openai" ? "OpenAI" : format === "anthropic" ? "Anthropic" : "Legacy Provider";
|
|
62
|
+
const trimmedName = provider.name?.trim();
|
|
63
|
+
const name =
|
|
64
|
+
trimmedName !== undefined && trimmedName !== "" ? trimmedName : `${fallbackName} ${index + 1}`;
|
|
65
|
+
const baseUrl = provider.baseUrl;
|
|
66
|
+
const anthropicBaseUrl =
|
|
67
|
+
provider.anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
|
|
68
|
+
const openaiBaseUrl =
|
|
69
|
+
provider.openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
|
|
70
|
+
|
|
71
|
+
const candidate: ProviderConfig = {
|
|
72
|
+
id: provider.id ?? randomUUID(),
|
|
73
|
+
name,
|
|
74
|
+
apiKey,
|
|
75
|
+
model: provider.model,
|
|
76
|
+
models: legacyModels(provider),
|
|
77
|
+
format,
|
|
78
|
+
baseUrl,
|
|
79
|
+
anthropicBaseUrl,
|
|
80
|
+
openaiBaseUrl,
|
|
81
|
+
authHeader: provider.authHeader ?? "bearer",
|
|
82
|
+
apiDocsUrl: provider.apiDocsUrl,
|
|
83
|
+
source: provider.source,
|
|
84
|
+
createdAt: provider.createdAt ?? now,
|
|
85
|
+
updatedAt: provider.updatedAt ?? now,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const parsed = ProviderConfigSchema.safeParse(candidate);
|
|
89
|
+
return parsed.success ? parsed.data : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function migrateFromDataDirConfig(targetStore: Conf<ProvidersStore>, dataDirPath: string): void {
|
|
93
|
+
if (process.env["AGENT_INSPECTOR_CONFIG_PATH"] !== undefined) return;
|
|
94
|
+
if (targetStore.get("providers", []).length > 0) return;
|
|
95
|
+
|
|
96
|
+
const legacyConfigPath = join(dataDirPath, "config.json");
|
|
97
|
+
if (!existsSync(legacyConfigPath)) return;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const raw = readFileSync(legacyConfigPath, "utf-8");
|
|
101
|
+
const parsedJson: unknown = JSON.parse(raw);
|
|
102
|
+
const parsed = LegacyProviderStoreSchema.safeParse(parsedJson);
|
|
103
|
+
if (!parsed.success) return;
|
|
104
|
+
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
const migrated: ProviderConfig[] = [];
|
|
107
|
+
for (let index = 0; index < parsed.data.providers.length; index++) {
|
|
108
|
+
const provider = parsed.data.providers[index];
|
|
109
|
+
if (provider === undefined) continue;
|
|
110
|
+
const current = migrateLegacyProvider(provider, index, now);
|
|
111
|
+
if (current !== null) migrated.push(current);
|
|
112
|
+
}
|
|
113
|
+
if (migrated.length === 0) return;
|
|
114
|
+
|
|
115
|
+
targetStore.set("providers", migrated);
|
|
116
|
+
logger.info(
|
|
117
|
+
`[providers] Migrated ${migrated.length} provider(s) from ${legacyConfigPath} to ${targetStore.path}`,
|
|
118
|
+
);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
logger.warn(`[providers] Data-dir config migration failed: ${String(err)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* One-time migration from the legacy conf v15 default location
|
|
126
|
+
* (`%APPDATA%\agent-inspector-nodejs\Config\config.json` on Windows,
|
|
127
|
+
* `$XDG_CONFIG_HOME/agent-inspector-nodejs/Config/config.json` on Linux).
|
|
128
|
+
* Older versions of agent-inspector let the `conf` library pick the path,
|
|
129
|
+
* which produced a project-suffixed directory under the OS user-config
|
|
130
|
+
* root. Users who upgrade now have their providers copied to the new
|
|
131
|
+
* `<dataDir>/providers.json` location; the legacy file is left in
|
|
132
|
+
* place for safety and can be deleted manually.
|
|
133
|
+
*
|
|
134
|
+
* The check is "new store is still empty" rather than "new file doesn't
|
|
135
|
+
* exist" because `Conf` materialises the defaults file on construction
|
|
136
|
+
* — so the file is always present immediately after the `new Conf()`
|
|
137
|
+
* call above, even on a brand-new install.
|
|
138
|
+
*/
|
|
139
|
+
function migrateFromLegacyConfLocation(targetStore: Conf<ProvidersStore>): void {
|
|
140
|
+
if (process.env["AGENT_INSPECTOR_CONFIG_PATH"] !== undefined) return;
|
|
141
|
+
if (hasExplicitDataDir()) return;
|
|
142
|
+
if (targetStore.get("providers", []).length > 0) return;
|
|
143
|
+
let legacyStore: Conf<ProvidersStore> | undefined;
|
|
144
|
+
try {
|
|
145
|
+
legacyStore = new Conf<ProvidersStore>({
|
|
146
|
+
projectName: "agent-inspector",
|
|
147
|
+
defaults: { providers: [] },
|
|
148
|
+
});
|
|
149
|
+
const legacyProviders = legacyStore.get("providers", []);
|
|
150
|
+
if (legacyProviders.length === 0) return;
|
|
151
|
+
targetStore.set("providers", legacyProviders);
|
|
152
|
+
logger.info(
|
|
153
|
+
`[providers] Migrated ${legacyProviders.length} provider(s) from ${legacyStore.path} to ${targetStore.path}`,
|
|
154
|
+
);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.warn(`[providers] Legacy migration failed: ${String(err)}`);
|
|
157
|
+
} finally {
|
|
158
|
+
legacyStore?._closeWatcher();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Using conf for storage - works in any Node.js environment without Electron
|
|
163
|
+
// Note: conf stores data in plain JSON. For production, consider additional encryption.
|
|
164
|
+
const configPath = process.env["AGENT_INSPECTOR_CONFIG_PATH"];
|
|
165
|
+
|
|
166
|
+
// The data directory is shared with the runtime config (`config.ts`).
|
|
167
|
+
// We pass `cwd` so conf writes the file as `<cwd>/<configName>.<ext>`,
|
|
168
|
+
// i.e. `<dataDir>/providers.json`. The runtime config lives at
|
|
169
|
+
// `<dataDir>/config.json`; the different filenames keep the two stores
|
|
170
|
+
// from overwriting each other. `mkdirSync(..., { recursive: true })` is
|
|
171
|
+
// a no-op when the directory already exists.
|
|
172
|
+
const dataDir = getDataDir();
|
|
173
|
+
if (configPath === undefined) {
|
|
174
|
+
mkdirSync(dataDir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const store = new Conf<ProvidersStore>({
|
|
178
|
+
cwd: dataDir,
|
|
179
|
+
configName: "providers",
|
|
180
|
+
defaults: {
|
|
181
|
+
providers: [],
|
|
182
|
+
},
|
|
183
|
+
...(configPath !== undefined ? { path: configPath } : {}),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Older builds stored providers in `<dataDir>/config.json`. Migrate that
|
|
187
|
+
// shape before checking the older Conf-managed default location.
|
|
188
|
+
migrateFromDataDirConfig(store, dataDir);
|
|
189
|
+
|
|
190
|
+
// Run the legacy migration once on module load. No-op when the new file
|
|
191
|
+
// already has data, when the user has set AGENT_INSPECTOR_CONFIG_PATH or
|
|
192
|
+
// an explicit data dir, or when the legacy file is empty.
|
|
193
|
+
migrateFromLegacyConfLocation(store);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Expands the old `{ format, baseUrl }` representation into the matching
|
|
197
|
+
* format-specific URL. Once either format-specific field exists, the record is
|
|
198
|
+
* already on the current schema and must be preserved exactly: an empty or
|
|
199
|
+
* missing counterpart means that protocol was intentionally not configured.
|
|
200
|
+
*/
|
|
201
|
+
export function migrateProvider(p: ProviderConfig): ProviderConfig {
|
|
202
|
+
if (p.anthropicBaseUrl !== undefined || p.openaiBaseUrl !== undefined) {
|
|
203
|
+
return p;
|
|
204
|
+
}
|
|
205
|
+
if (p.format === undefined || p.baseUrl === undefined || p.baseUrl === "") {
|
|
206
|
+
return p;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return p.format === "openai"
|
|
210
|
+
? { ...p, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl }
|
|
211
|
+
: { ...p, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
|
|
216
|
+
* Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
|
|
217
|
+
*/
|
|
218
|
+
function migrateProviders(): void {
|
|
219
|
+
const providers = store.get("providers", []);
|
|
220
|
+
const updated = providers.map(migrateProvider);
|
|
221
|
+
const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
|
|
222
|
+
|
|
223
|
+
if (migrated) {
|
|
224
|
+
store.set("providers", updated);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Run migration on module load
|
|
229
|
+
migrateProviders();
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Migrate: merge providers with identical (apiKey, anthropicBaseUrl, openaiBaseUrl)
|
|
233
|
+
* into multi-model providers. Existing single-model providers get their `model`
|
|
234
|
+
* value promoted to `models: [model]`.
|
|
235
|
+
*/
|
|
236
|
+
function migrateMultiModel(): void {
|
|
237
|
+
const providers = store.get("providers", []);
|
|
238
|
+
if (providers.length === 0) return;
|
|
239
|
+
let changed = false;
|
|
240
|
+
|
|
241
|
+
// Step 1: Promote single model to models array for any provider missing models
|
|
242
|
+
const promoted = providers.map((p) => {
|
|
243
|
+
if (p.models !== undefined && p.models.length > 0) return p;
|
|
244
|
+
const models = p.model !== undefined && p.model !== "" ? [p.model] : [];
|
|
245
|
+
changed = true;
|
|
246
|
+
return { ...p, models };
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Step 2: Merge providers with same (apiKey, anthropicBaseUrl, openaiBaseUrl)
|
|
250
|
+
const groups = new Map<string, ProviderConfig[]>();
|
|
251
|
+
for (const p of promoted) {
|
|
252
|
+
const key = `${p.apiKey}::${p.anthropicBaseUrl ?? ""}::${p.openaiBaseUrl ?? ""}`;
|
|
253
|
+
const group = groups.get(key);
|
|
254
|
+
if (group !== undefined) {
|
|
255
|
+
group.push(p);
|
|
256
|
+
} else {
|
|
257
|
+
groups.set(key, [p]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const merged: ProviderConfig[] = [];
|
|
262
|
+
for (const group of groups.values()) {
|
|
263
|
+
const first = group[0];
|
|
264
|
+
if (group.length === 1 && first !== undefined) {
|
|
265
|
+
merged.push(first);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
changed = true;
|
|
269
|
+
// Merge: keep the earliest provider's metadata, combine all models (deduplicated)
|
|
270
|
+
const sorted = group.toSorted(
|
|
271
|
+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
|
|
272
|
+
);
|
|
273
|
+
const earliest = sorted[0];
|
|
274
|
+
if (earliest === undefined) continue;
|
|
275
|
+
const allModels = new Set<string>();
|
|
276
|
+
for (const p of sorted) {
|
|
277
|
+
for (const m of p.models ?? []) {
|
|
278
|
+
if (m !== "") allModels.add(m);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Take first non-empty source
|
|
282
|
+
const mergedSource = sorted.find((p) => p.source !== undefined)?.source;
|
|
283
|
+
merged.push({
|
|
284
|
+
...earliest,
|
|
285
|
+
models: [...allModels],
|
|
286
|
+
source: mergedSource,
|
|
287
|
+
updatedAt: new Date().toISOString(),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (changed && merged.length > 0) {
|
|
292
|
+
// Safety backup before writing
|
|
293
|
+
try {
|
|
294
|
+
writeFileSync(`${store.path}.bak`, readFileSync(store.path, "utf-8"));
|
|
295
|
+
} catch {
|
|
296
|
+
// best-effort, ignore
|
|
297
|
+
}
|
|
298
|
+
store.set("providers", merged);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
migrateMultiModel();
|
|
303
|
+
|
|
304
|
+
// Override with JSON env var if provided (for testing)
|
|
305
|
+
const providersJson = process.env["AGENT_INSPECTOR_PROVIDERS_JSON"];
|
|
306
|
+
if (providersJson !== undefined) {
|
|
307
|
+
try {
|
|
308
|
+
const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
|
|
309
|
+
if (parsed.success) {
|
|
310
|
+
// Apply migration to JSON providers (e.g., convert baseUrl to openaiBaseUrl/anthropicBaseUrl)
|
|
311
|
+
const migrated = parsed.data.map(migrateProvider);
|
|
312
|
+
store.set("providers", migrated);
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
// Ignore invalid JSON
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function getProviders(): ProviderConfig[] {
|
|
320
|
+
return store.get("providers", []);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function getProvider(id: string): ProviderConfig | undefined {
|
|
324
|
+
const providers = getProviders();
|
|
325
|
+
return providers.find((p) => p.id === id);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Normalizes an API key by stripping "Bearer " prefix if present.
|
|
330
|
+
*/
|
|
331
|
+
export function normalizeApiKey(apiKey: string): string {
|
|
332
|
+
return apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function addProvider(
|
|
336
|
+
name: string,
|
|
337
|
+
apiKey: string,
|
|
338
|
+
format?: "anthropic" | "openai",
|
|
339
|
+
baseUrl?: string,
|
|
340
|
+
models?: string[],
|
|
341
|
+
authHeader?: "bearer" | "x-api-key",
|
|
342
|
+
apiDocsUrl?: string,
|
|
343
|
+
anthropicBaseUrl?: string,
|
|
344
|
+
openaiBaseUrl?: string,
|
|
345
|
+
source?: "company" | "personal",
|
|
346
|
+
): ProviderConfig {
|
|
347
|
+
const providers = getProviders();
|
|
348
|
+
const normalizedKey = normalizeApiKey(apiKey);
|
|
349
|
+
const resolvedAnthropicUrl =
|
|
350
|
+
anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
|
|
351
|
+
const resolvedOpenaiUrl =
|
|
352
|
+
openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
|
|
353
|
+
const now = new Date().toISOString();
|
|
354
|
+
|
|
355
|
+
// If a provider with the same (apiKey, anthropicBaseUrl, openaiBaseUrl) already exists,
|
|
356
|
+
// merge the new models into it instead of creating a duplicate.
|
|
357
|
+
const existing = providers.find(
|
|
358
|
+
(p) =>
|
|
359
|
+
p.apiKey === normalizedKey &&
|
|
360
|
+
(p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") &&
|
|
361
|
+
(p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? ""),
|
|
362
|
+
);
|
|
363
|
+
if (existing) {
|
|
364
|
+
const newModels = (models ?? []).filter((m) => m !== "");
|
|
365
|
+
if (newModels.length > 0) {
|
|
366
|
+
const mergedModels = new Set(existing.models ?? []);
|
|
367
|
+
for (const m of newModels) mergedModels.add(m);
|
|
368
|
+
existing.models = [...mergedModels];
|
|
369
|
+
existing.updatedAt = now;
|
|
370
|
+
store.set("providers", providers);
|
|
371
|
+
}
|
|
372
|
+
return existing;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const newProvider: ProviderConfig = {
|
|
376
|
+
id: randomUUID(),
|
|
377
|
+
name,
|
|
378
|
+
apiKey: normalizedKey,
|
|
379
|
+
format:
|
|
380
|
+
format ??
|
|
381
|
+
(anthropicBaseUrl !== undefined
|
|
382
|
+
? "anthropic"
|
|
383
|
+
: openaiBaseUrl !== undefined
|
|
384
|
+
? "openai"
|
|
385
|
+
: undefined),
|
|
386
|
+
baseUrl,
|
|
387
|
+
models: models ?? [],
|
|
388
|
+
authHeader: authHeader ?? "bearer",
|
|
389
|
+
apiDocsUrl,
|
|
390
|
+
createdAt: now,
|
|
391
|
+
updatedAt: now,
|
|
392
|
+
anthropicBaseUrl: resolvedAnthropicUrl,
|
|
393
|
+
openaiBaseUrl: resolvedOpenaiUrl,
|
|
394
|
+
source,
|
|
395
|
+
};
|
|
396
|
+
providers.push(newProvider);
|
|
397
|
+
store.set("providers", providers);
|
|
398
|
+
return newProvider;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function updateProvider(
|
|
402
|
+
id: string,
|
|
403
|
+
updates: Partial<Omit<ProviderConfig, "id" | "createdAt">>,
|
|
404
|
+
): ProviderConfig | null {
|
|
405
|
+
const providers = getProviders();
|
|
406
|
+
const existing = providers.find((p) => p.id === id);
|
|
407
|
+
if (!existing) return null;
|
|
408
|
+
|
|
409
|
+
const updated: ProviderConfig = {
|
|
410
|
+
id: existing.id,
|
|
411
|
+
name: updates.name ?? existing.name,
|
|
412
|
+
apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
|
|
413
|
+
models: updates.models !== undefined ? updates.models : existing.models,
|
|
414
|
+
format: updates.format ?? existing.format,
|
|
415
|
+
baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
|
|
416
|
+
authHeader: updates.authHeader ?? existing.authHeader,
|
|
417
|
+
apiDocsUrl: updates.apiDocsUrl ?? existing.apiDocsUrl,
|
|
418
|
+
createdAt: existing.createdAt,
|
|
419
|
+
updatedAt: new Date().toISOString(),
|
|
420
|
+
// Handle format-specific URLs
|
|
421
|
+
anthropicBaseUrl:
|
|
422
|
+
updates.anthropicBaseUrl !== undefined ? updates.anthropicBaseUrl : existing.anthropicBaseUrl,
|
|
423
|
+
openaiBaseUrl:
|
|
424
|
+
updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
|
|
425
|
+
source: updates.source !== undefined ? updates.source : existing.source,
|
|
426
|
+
};
|
|
427
|
+
const index = providers.findIndex((p) => p.id === id);
|
|
428
|
+
providers[index] = updated;
|
|
429
|
+
store.set("providers", providers);
|
|
430
|
+
return updated;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function deleteProvider(id: string): boolean {
|
|
434
|
+
const providers = getProviders();
|
|
435
|
+
const filtered = providers.filter((p) => p.id !== id);
|
|
436
|
+
if (filtered.length === providers.length) return false;
|
|
437
|
+
|
|
438
|
+
store.set("providers", filtered);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Export all providers as a JSON string (without API keys for security).
|
|
444
|
+
*/
|
|
445
|
+
export function exportProviders(): string {
|
|
446
|
+
const providers = getProviders();
|
|
447
|
+
// Mask API keys for security
|
|
448
|
+
const safeProviders = providers.map(({ apiKey, ...rest }) => ({
|
|
449
|
+
...rest,
|
|
450
|
+
apiKey: maskApiKey(apiKey),
|
|
451
|
+
}));
|
|
452
|
+
return JSON.stringify(safeProviders, null, 2);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Export all providers as a JSON string including API keys (user must opt-in).
|
|
457
|
+
*/
|
|
458
|
+
export function exportProvidersWithKeys(): string {
|
|
459
|
+
return JSON.stringify(getProviders(), null, 2);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Import providers from a JSON string.
|
|
464
|
+
* Returns { imported: number, errors: string[] }
|
|
465
|
+
*/
|
|
466
|
+
export function importProviders(json: string): { imported: number; errors: string[] } {
|
|
467
|
+
const errors: string[] = [];
|
|
468
|
+
let imported = 0;
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
const parsed: unknown = JSON.parse(json);
|
|
472
|
+
|
|
473
|
+
// Use Zod to validate the structure
|
|
474
|
+
const ImportSchema = z.union([
|
|
475
|
+
z.array(ProviderConfigSchema),
|
|
476
|
+
z.object({
|
|
477
|
+
providers: z.array(ProviderConfigSchema),
|
|
478
|
+
}),
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
const parseResult = ImportSchema.safeParse(parsed);
|
|
482
|
+
if (!parseResult.success) {
|
|
483
|
+
return {
|
|
484
|
+
imported: 0,
|
|
485
|
+
errors: [`Invalid format: ${parseResult.error.message}`],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const providerArray = Array.isArray(parseResult.data)
|
|
490
|
+
? parseResult.data
|
|
491
|
+
: parseResult.data.providers;
|
|
492
|
+
|
|
493
|
+
// Snapshot existing providers once
|
|
494
|
+
const existingProviders = getProviders();
|
|
495
|
+
|
|
496
|
+
// Build a Set of existing (name, anthropicBaseUrl) keys for O(1) duplicate lookup
|
|
497
|
+
const existingKeys = new Set<string>();
|
|
498
|
+
for (const p of existingProviders) {
|
|
499
|
+
existingKeys.add(`${p.name}::${p.anthropicBaseUrl ?? ""}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const newProviders: ProviderConfig[] = [];
|
|
503
|
+
|
|
504
|
+
for (let i = 0; i < providerArray.length; i++) {
|
|
505
|
+
const item = providerArray[i];
|
|
506
|
+
const result = ProviderConfigSchema.safeParse(item);
|
|
507
|
+
|
|
508
|
+
if (!result.success) {
|
|
509
|
+
errors.push(`Provider ${i + 1}: ${result.error.message}`);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const provider = result.data;
|
|
514
|
+
const key = `${provider.name}::${provider.anthropicBaseUrl ?? ""}`;
|
|
515
|
+
|
|
516
|
+
if (existingKeys.has(key)) {
|
|
517
|
+
errors.push(`Provider "${provider.name}" already exists, skipping`);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Generate new ID and add
|
|
522
|
+
const newProvider: ProviderConfig = {
|
|
523
|
+
...provider,
|
|
524
|
+
id: randomUUID(),
|
|
525
|
+
createdAt: new Date().toISOString(),
|
|
526
|
+
updatedAt: new Date().toISOString(),
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
newProviders.push(newProvider);
|
|
530
|
+
existingKeys.add(key);
|
|
531
|
+
imported++;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Single write: append all new providers at once
|
|
535
|
+
if (newProviders.length > 0) {
|
|
536
|
+
store.set("providers", [...existingProviders, ...newProviders]);
|
|
537
|
+
}
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return {
|
|
540
|
+
imported: 0,
|
|
541
|
+
errors: [`Failed to parse JSON: ${err instanceof Error ? err.message : String(err)}`],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return { imported, errors };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function maskApiKey(apiKey: string): string {
|
|
549
|
+
if (apiKey.length <= 8) return "••••••••";
|
|
550
|
+
return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Converts display model name to API usage name based on provider-specific rules.
|
|
555
|
+
* Default behavior: returns model as-is.
|
|
556
|
+
* MiniMax: replaces spaces with hyphens.
|
|
557
|
+
*/
|
|
558
|
+
export function getModelUsageName(model: string, providerName?: string): string {
|
|
559
|
+
if (
|
|
560
|
+
providerName !== undefined &&
|
|
561
|
+
providerName !== "" &&
|
|
562
|
+
providerName.toLowerCase().includes("minimax")
|
|
563
|
+
) {
|
|
564
|
+
return model.replace(/ /g, "-");
|
|
565
|
+
}
|
|
566
|
+
return model;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Normalize a model name for comparison: lowercase, replace whitespace with hyphens.
|
|
571
|
+
*/
|
|
572
|
+
function normalizeModelName(name: string): string {
|
|
573
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Finds a provider by model name using three strategies:
|
|
578
|
+
* 1. Case-insensitive prefix match against "{provider.name}-" (e.g., "deepseek-" matches "deepseek-*")
|
|
579
|
+
* 2. Case-insensitive match against provider.model field (with whitespace/hyphen normalization)
|
|
580
|
+
* 3. Fallback: concatenate provider name with provider's model and check if the request model matches
|
|
581
|
+
* this concatenation (handles "MiniMax" + "M3" → "MiniMax-M3" pattern when case differs)
|
|
582
|
+
*/
|
|
583
|
+
export function findProviderByModel(model: string): ProviderConfig | null {
|
|
584
|
+
const providers = getProviders();
|
|
585
|
+
const modelLower = model.toLowerCase();
|
|
586
|
+
const modelNormalized = normalizeModelName(model);
|
|
587
|
+
|
|
588
|
+
for (const provider of providers) {
|
|
589
|
+
// Strategy 1: provider name prefix match
|
|
590
|
+
const providerPrefix = (provider.name + "-").toLowerCase();
|
|
591
|
+
if (modelLower.startsWith(providerPrefix)) {
|
|
592
|
+
return provider;
|
|
593
|
+
}
|
|
594
|
+
// Strategy 2: match against provider.models array with normalization
|
|
595
|
+
const modelList = provider.models ?? (provider.model !== undefined ? [provider.model] : []);
|
|
596
|
+
for (const m of modelList) {
|
|
597
|
+
if (m !== "" && modelNormalized === normalizeModelName(m)) {
|
|
598
|
+
return provider;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// Strategy 3: fallback - concatenate provider name with model part and compare
|
|
602
|
+
for (const m of modelList) {
|
|
603
|
+
if (m !== "") {
|
|
604
|
+
const modelPart = modelNormalized.replace(normalizeModelName(provider.name), "");
|
|
605
|
+
const concatenated = normalizeModelName(provider.name) + modelPart;
|
|
606
|
+
if (modelNormalized === concatenated) {
|
|
607
|
+
return provider;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|