@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,50 @@
|
|
|
1
|
+
import type { CapturedLog } from "../../schemas";
|
|
2
|
+
import type { ProviderProtocol } from "../protocol";
|
|
3
|
+
import { registry } from "../providers";
|
|
4
|
+
import { extractOpenAIStream } from "./stream";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_OPENAI_UPSTREAM = "https://api.openai.com/v1";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* OpenAI Provider Implementation
|
|
10
|
+
*/
|
|
11
|
+
export const openaiProvider: ProviderProtocol = {
|
|
12
|
+
name: "openai",
|
|
13
|
+
|
|
14
|
+
matches(model: string): boolean {
|
|
15
|
+
const modelLower = model.toLowerCase();
|
|
16
|
+
// Match OpenAI models
|
|
17
|
+
if (modelLower.startsWith("openai-")) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Also match common OpenAI model names
|
|
21
|
+
if (
|
|
22
|
+
modelLower.includes("gpt-") ||
|
|
23
|
+
modelLower.includes("o1") ||
|
|
24
|
+
modelLower.includes("o2") ||
|
|
25
|
+
modelLower.includes("o3")
|
|
26
|
+
) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
getUpstreamBase(_isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string {
|
|
33
|
+
if (providerConfig?.baseUrl !== undefined) {
|
|
34
|
+
return providerConfig.baseUrl;
|
|
35
|
+
}
|
|
36
|
+
return DEFAULT_OPENAI_UPSTREAM;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
extractStream(
|
|
40
|
+
raw: string,
|
|
41
|
+
log: CapturedLog,
|
|
42
|
+
fallbackModel?: string,
|
|
43
|
+
collectChunks?: boolean,
|
|
44
|
+
): string {
|
|
45
|
+
return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Register the provider
|
|
50
|
+
registry.register(openaiProvider);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { JsonValueSchema } from "../jsonSchema";
|
|
3
|
+
|
|
4
|
+
// OpenAI Chat Completions Schemas
|
|
5
|
+
|
|
6
|
+
export const OpenAIMessageContent = z.union([
|
|
7
|
+
z.string(),
|
|
8
|
+
z.array(
|
|
9
|
+
z.discriminatedUnion("type", [
|
|
10
|
+
z.object({ type: z.literal("text"), text: z.string() }),
|
|
11
|
+
z.object({
|
|
12
|
+
type: z.literal("image_url"),
|
|
13
|
+
image_url: z.object({ url: z.string(), detail: z.string().optional() }),
|
|
14
|
+
}),
|
|
15
|
+
]),
|
|
16
|
+
),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const OpenAIMessage = z.object({
|
|
20
|
+
role: z.enum(["system", "user", "assistant", "tool"]),
|
|
21
|
+
content: OpenAIMessageContent,
|
|
22
|
+
name: z.string().optional(),
|
|
23
|
+
reasoning_content: z.string().optional(),
|
|
24
|
+
thinking: z.string().optional(), // Some providers use 'thinking' field
|
|
25
|
+
think: z.string().optional(), // MiniMax uses 'think' field
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const OpenAIFunctionCall = z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
arguments: z.string(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** A single OpenAI tool_call (the modern multi-tool form, returned by most
|
|
34
|
+
* providers in `choices[].message.tool_calls`). `function.arguments` is
|
|
35
|
+
* a JSON-encoded string per the OpenAI spec — we keep it as a string at
|
|
36
|
+
* parse time and let the renderer decide whether to JSON-parse it for
|
|
37
|
+
* display. */
|
|
38
|
+
export const OpenAIToolCallSchema = z.object({
|
|
39
|
+
index: z.number().optional(),
|
|
40
|
+
id: z.string().optional(),
|
|
41
|
+
type: z.literal("function").optional(),
|
|
42
|
+
function: z.object({
|
|
43
|
+
name: z.string().optional(),
|
|
44
|
+
arguments: z.string().optional(),
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type OpenAIToolCall = z.infer<typeof OpenAIToolCallSchema>;
|
|
49
|
+
|
|
50
|
+
const OpenAIMessageWithFunctionCall = OpenAIMessage.extend({
|
|
51
|
+
content: z
|
|
52
|
+
.union([z.string(), z.array(z.object({ type: z.literal("text"), text: z.string() }))])
|
|
53
|
+
.optional(),
|
|
54
|
+
function_call: OpenAIFunctionCall.optional(),
|
|
55
|
+
tool_calls: z.array(OpenAIToolCallSchema).optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const OpenAIToolDefinition = z.object({
|
|
59
|
+
type: z.literal("function"),
|
|
60
|
+
function: z.object({
|
|
61
|
+
name: z.string(),
|
|
62
|
+
description: z.string().optional(),
|
|
63
|
+
parameters: z.record(z.string(), JsonValueSchema),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const OpenAIRequestSchema = z.object({
|
|
68
|
+
model: z.string(),
|
|
69
|
+
messages: z.array(OpenAIMessage),
|
|
70
|
+
temperature: z.number().optional(),
|
|
71
|
+
max_tokens: z.number().optional(),
|
|
72
|
+
stream: z.boolean().optional(),
|
|
73
|
+
tools: z.array(OpenAIToolDefinition).optional(),
|
|
74
|
+
tool_choice: z
|
|
75
|
+
.union([
|
|
76
|
+
z.enum(["auto", "none", "required"]),
|
|
77
|
+
z.object({ type: z.literal("auto") }),
|
|
78
|
+
z.object({ type: z.literal("none") }),
|
|
79
|
+
z.object({ type: z.literal("function"), function: z.object({ name: z.string() }) }),
|
|
80
|
+
])
|
|
81
|
+
.optional(),
|
|
82
|
+
user: z.string().optional(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const OpenAIChoiceDelta = z.object({
|
|
86
|
+
role: z.enum(["assistant"]).optional(),
|
|
87
|
+
content: z.string().nullable().optional(),
|
|
88
|
+
reasoning_content: z.string().nullable().optional(),
|
|
89
|
+
thinking: z.string().nullable().optional(), // Some providers use 'thinking' field
|
|
90
|
+
think: z.string().nullable().optional(), // MiniMax uses 'think' field
|
|
91
|
+
function_call: z
|
|
92
|
+
.object({ name: z.string().optional(), arguments: z.string().optional() })
|
|
93
|
+
.nullable()
|
|
94
|
+
.optional(),
|
|
95
|
+
tool_calls: z
|
|
96
|
+
.array(
|
|
97
|
+
z.object({
|
|
98
|
+
index: z.number(),
|
|
99
|
+
id: z.string().optional(),
|
|
100
|
+
type: z.literal("function").optional(),
|
|
101
|
+
function: z.object({
|
|
102
|
+
name: z.string().optional(),
|
|
103
|
+
arguments: z.string().optional(),
|
|
104
|
+
}),
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
.nullable()
|
|
108
|
+
.optional(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const OpenAIChoice = z.object({
|
|
112
|
+
index: z.number(),
|
|
113
|
+
message: z
|
|
114
|
+
.object({
|
|
115
|
+
role: z.enum(["assistant"]),
|
|
116
|
+
content: z.string().nullable(),
|
|
117
|
+
reasoning_content: z.string().optional(),
|
|
118
|
+
thinking: z.string().optional(), // Some providers use 'thinking' field in message
|
|
119
|
+
think: z.string().optional(), // MiniMax uses 'think' field in message
|
|
120
|
+
function_call: z.object({ name: z.string(), arguments: z.string() }).nullable().optional(),
|
|
121
|
+
tool_calls: z.array(OpenAIToolCallSchema).optional(),
|
|
122
|
+
})
|
|
123
|
+
.optional(),
|
|
124
|
+
delta: OpenAIChoiceDelta.optional(),
|
|
125
|
+
finish_reason: z.string().nullable(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const OpenAIResponseSchema = z
|
|
129
|
+
.object({
|
|
130
|
+
id: z.string(),
|
|
131
|
+
object: z.literal("chat.completion"),
|
|
132
|
+
created: z.number(),
|
|
133
|
+
model: z.string(),
|
|
134
|
+
choices: z.array(OpenAIChoice),
|
|
135
|
+
usage: z
|
|
136
|
+
.object({
|
|
137
|
+
prompt_tokens: z.number().nullable().optional(),
|
|
138
|
+
completion_tokens: z.number().nullable().optional(),
|
|
139
|
+
total_tokens: z.number().nullable().optional(),
|
|
140
|
+
})
|
|
141
|
+
.passthrough(), // Allow extra fields like prompt_tokens_details, completion_tokens_details
|
|
142
|
+
service_tier: z.string().nullable().optional(),
|
|
143
|
+
system_fingerprint: z.string().nullable().optional(),
|
|
144
|
+
})
|
|
145
|
+
.passthrough(); // Allow extra fields like refusal, annotations, audio
|
|
146
|
+
|
|
147
|
+
// Schema for actual OpenAI SSE chunk format (not wrapped in event/data)
|
|
148
|
+
export const OpenAISSERawChunkSchema = z
|
|
149
|
+
.object({
|
|
150
|
+
id: z.string(),
|
|
151
|
+
object: z.literal("chat.completion.chunk"),
|
|
152
|
+
created: z.number(),
|
|
153
|
+
model: z.string(),
|
|
154
|
+
choices: z.array(
|
|
155
|
+
z.object({
|
|
156
|
+
index: z.number(),
|
|
157
|
+
delta: OpenAIChoiceDelta,
|
|
158
|
+
finish_reason: z.string().nullable().optional(),
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
usage: z
|
|
162
|
+
.object({
|
|
163
|
+
prompt_tokens: z.number().nullable().optional(),
|
|
164
|
+
completion_tokens: z.number().nullable().optional(),
|
|
165
|
+
total_tokens: z.number().nullable().optional(),
|
|
166
|
+
})
|
|
167
|
+
.passthrough()
|
|
168
|
+
.nullable()
|
|
169
|
+
.optional(),
|
|
170
|
+
})
|
|
171
|
+
.passthrough(); // Allow extra fields in chunks
|
|
172
|
+
|
|
173
|
+
export type OpenAIResponse = z.infer<typeof OpenAIResponseSchema>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parse an OpenAI response body.
|
|
177
|
+
*/
|
|
178
|
+
export function parseOpenAIResponse(rawBody: string): OpenAIResponse | null {
|
|
179
|
+
try {
|
|
180
|
+
const json: unknown = JSON.parse(rawBody);
|
|
181
|
+
const result = OpenAIResponseSchema.safeParse(json);
|
|
182
|
+
if (result.success) return result.data;
|
|
183
|
+
return null;
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { CapturedLog, JsonValue } from "../../schemas";
|
|
2
|
+
import { trustAsJsonValue } from "../jsonSchema";
|
|
3
|
+
import { OpenAISSERawChunkSchema } from "./schemas";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract OpenAI streaming response and return normalized JSON.
|
|
7
|
+
* Optionally collects SSE chunks into log.streamingChunks for debugging/display.
|
|
8
|
+
*/
|
|
9
|
+
export function extractOpenAIStream(
|
|
10
|
+
raw: string,
|
|
11
|
+
log: CapturedLog,
|
|
12
|
+
fallbackModel?: string,
|
|
13
|
+
collectChunks: boolean = true,
|
|
14
|
+
): string {
|
|
15
|
+
let id = "";
|
|
16
|
+
let model = "";
|
|
17
|
+
let completionContent = "";
|
|
18
|
+
let reasoningContent = "";
|
|
19
|
+
let finishReason: string | null = null;
|
|
20
|
+
let promptTokens = 0;
|
|
21
|
+
let completionTokens = 0;
|
|
22
|
+
let usageCaptured = false;
|
|
23
|
+
let started = false;
|
|
24
|
+
type ToolCallEntry = {
|
|
25
|
+
index: number;
|
|
26
|
+
id?: string;
|
|
27
|
+
type: "function";
|
|
28
|
+
function: { name: string; arguments: string };
|
|
29
|
+
};
|
|
30
|
+
// Map keyed by `index` so delta accumulation is O(1) per delta instead of
|
|
31
|
+
// O(N) (the previous Array#find). Map preserves insertion order, so the
|
|
32
|
+
// final `[...toolCalls.values()]` keeps tool calls in the order they were
|
|
33
|
+
// first seen.
|
|
34
|
+
const toolCalls: Map<number, ToolCallEntry> = new Map();
|
|
35
|
+
|
|
36
|
+
// Chunk collection state
|
|
37
|
+
const MAX_CHUNKS = 1000;
|
|
38
|
+
let chunkIndex = 0;
|
|
39
|
+
let streamStartMs = 0;
|
|
40
|
+
const chunks: Array<{ index: number; timestamp: number; type: string; data: JsonValue }> = [];
|
|
41
|
+
|
|
42
|
+
for (const line of raw.split("\n")) {
|
|
43
|
+
const trimmedLine = line.trim();
|
|
44
|
+
if (!trimmedLine.startsWith("data: ")) continue;
|
|
45
|
+
const dataStr = trimmedLine.slice(6);
|
|
46
|
+
if (dataStr === "[DONE]") break;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const parsed: unknown = JSON.parse(dataStr);
|
|
50
|
+
// Check for error object in SSE stream before trying to parse as chunk
|
|
51
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
52
|
+
const errorDesc = Object.getOwnPropertyDescriptor(parsed, "error");
|
|
53
|
+
if (
|
|
54
|
+
errorDesc !== undefined &&
|
|
55
|
+
typeof errorDesc.value === "object" &&
|
|
56
|
+
errorDesc.value !== null
|
|
57
|
+
) {
|
|
58
|
+
const msgDesc = Object.getOwnPropertyDescriptor(errorDesc.value, "message");
|
|
59
|
+
if (msgDesc !== undefined && typeof msgDesc.value === "string") {
|
|
60
|
+
log.error = msgDesc.value;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const chunkResult = OpenAISSERawChunkSchema.safeParse(parsed);
|
|
66
|
+
if (!chunkResult.success) continue;
|
|
67
|
+
const chunk = chunkResult.data;
|
|
68
|
+
|
|
69
|
+
// Track stream start for timestamps
|
|
70
|
+
if (chunkIndex === 0) streamStartMs = Date.now();
|
|
71
|
+
|
|
72
|
+
// Collect chunks if enabled
|
|
73
|
+
if (collectChunks === true && chunks.length < MAX_CHUNKS) {
|
|
74
|
+
// `chunk` has already been validated by OpenAISSERawChunkSchema above;
|
|
75
|
+
// skipping the recursive JsonValue walk avoids a second full-tree zod
|
|
76
|
+
// parse on every SSE event. See trustAsJsonValue for the rationale.
|
|
77
|
+
chunks.push({
|
|
78
|
+
index: chunkIndex,
|
|
79
|
+
timestamp: Date.now() - streamStartMs,
|
|
80
|
+
type: "chat.completion.chunk",
|
|
81
|
+
data: trustAsJsonValue(chunk),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
chunkIndex++;
|
|
85
|
+
|
|
86
|
+
if (!started) {
|
|
87
|
+
id = chunk.id;
|
|
88
|
+
model = chunk.model;
|
|
89
|
+
started = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!usageCaptured && chunk.usage !== undefined && chunk.usage !== null) {
|
|
93
|
+
promptTokens = chunk.usage.prompt_tokens ?? 0;
|
|
94
|
+
completionTokens = chunk.usage.completion_tokens ?? 0;
|
|
95
|
+
log.inputTokens = promptTokens;
|
|
96
|
+
// Extract cached_tokens from raw parsed object (passthrough field in usage)
|
|
97
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
98
|
+
const usageDesc = Object.getOwnPropertyDescriptor(parsed, "usage");
|
|
99
|
+
if (
|
|
100
|
+
usageDesc !== undefined &&
|
|
101
|
+
typeof usageDesc.value === "object" &&
|
|
102
|
+
usageDesc.value !== null
|
|
103
|
+
) {
|
|
104
|
+
const detailsDesc = Object.getOwnPropertyDescriptor(
|
|
105
|
+
usageDesc.value,
|
|
106
|
+
"prompt_tokens_details",
|
|
107
|
+
);
|
|
108
|
+
if (
|
|
109
|
+
detailsDesc !== undefined &&
|
|
110
|
+
typeof detailsDesc.value === "object" &&
|
|
111
|
+
detailsDesc.value !== null
|
|
112
|
+
) {
|
|
113
|
+
const cacheDesc = Object.getOwnPropertyDescriptor(detailsDesc.value, "cached_tokens");
|
|
114
|
+
if (cacheDesc !== undefined && typeof cacheDesc.value === "number") {
|
|
115
|
+
log.cacheReadInputTokens = cacheDesc.value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
usageCaptured = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const choice of chunk.choices) {
|
|
124
|
+
const delta = choice.delta;
|
|
125
|
+
if (delta.content !== undefined && delta.content !== null) {
|
|
126
|
+
completionContent += delta.content;
|
|
127
|
+
}
|
|
128
|
+
if (delta.reasoning_content !== undefined && delta.reasoning_content !== null) {
|
|
129
|
+
reasoningContent += delta.reasoning_content;
|
|
130
|
+
}
|
|
131
|
+
if (delta.thinking !== undefined && delta.thinking !== null) {
|
|
132
|
+
reasoningContent += delta.thinking;
|
|
133
|
+
}
|
|
134
|
+
// MiniMax uses 'think' field for thinking content - check via bracket notation
|
|
135
|
+
const thinkValue = delta.think;
|
|
136
|
+
if (thinkValue !== undefined && thinkValue !== null) {
|
|
137
|
+
reasoningContent += thinkValue;
|
|
138
|
+
}
|
|
139
|
+
if (choice.finish_reason !== undefined && choice.finish_reason !== null) {
|
|
140
|
+
finishReason = choice.finish_reason;
|
|
141
|
+
}
|
|
142
|
+
// Accumulate tool_calls
|
|
143
|
+
if (delta.tool_calls !== undefined && delta.tool_calls !== null) {
|
|
144
|
+
for (const tc of delta.tool_calls) {
|
|
145
|
+
// Find or create tool call entry by index
|
|
146
|
+
let existing = toolCalls.get(tc.index);
|
|
147
|
+
if (existing === undefined) {
|
|
148
|
+
existing = {
|
|
149
|
+
index: tc.index,
|
|
150
|
+
type: "function",
|
|
151
|
+
function: { name: "", arguments: "" },
|
|
152
|
+
};
|
|
153
|
+
toolCalls.set(tc.index, existing);
|
|
154
|
+
}
|
|
155
|
+
if (tc.id !== undefined) existing.id = tc.id;
|
|
156
|
+
if (tc.function?.name !== undefined) existing.function.name += tc.function.name;
|
|
157
|
+
if (tc.function?.arguments !== undefined)
|
|
158
|
+
existing.function.arguments += tc.function.arguments;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// non-JSON SSE line, skip
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Store collected chunks on the log
|
|
168
|
+
if (collectChunks === true) {
|
|
169
|
+
log.streamingChunks = {
|
|
170
|
+
chunks,
|
|
171
|
+
truncated: chunkIndex > MAX_CHUNKS,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!usageCaptured) {
|
|
176
|
+
completionTokens = Math.ceil(completionContent.length / 4);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
log.outputTokens = completionTokens;
|
|
180
|
+
|
|
181
|
+
const message: Record<string, unknown> = {
|
|
182
|
+
role: "assistant",
|
|
183
|
+
content: completionContent,
|
|
184
|
+
};
|
|
185
|
+
if (reasoningContent) message.reasoning_content = reasoningContent;
|
|
186
|
+
if (toolCalls.size > 0) message.tool_calls = [...toolCalls.values()];
|
|
187
|
+
|
|
188
|
+
return JSON.stringify({
|
|
189
|
+
id,
|
|
190
|
+
object: "chat.completion",
|
|
191
|
+
created: Math.floor(Date.now() / 1000),
|
|
192
|
+
model: model !== "" ? model : (fallbackModel ?? ""),
|
|
193
|
+
choices: [
|
|
194
|
+
{
|
|
195
|
+
index: 0,
|
|
196
|
+
message,
|
|
197
|
+
finish_reason: finishReason,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
usage: {
|
|
201
|
+
prompt_tokens: promptTokens,
|
|
202
|
+
completion_tokens: completionTokens,
|
|
203
|
+
total_tokens: promptTokens + completionTokens,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CapturedLog, RequestFormat } from "../schemas";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provider Protocol Interface
|
|
5
|
+
*
|
|
6
|
+
* Defines the contract that all providers must implement to support
|
|
7
|
+
* different LLM APIs (Anthropic, OpenAI, etc.).
|
|
8
|
+
*/
|
|
9
|
+
export type ProviderProtocol = {
|
|
10
|
+
/** Provider identifier (e.g., "anthropic", "openai") */
|
|
11
|
+
name: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if this provider handles the given model.
|
|
15
|
+
* @param model - Model name from request
|
|
16
|
+
* @returns true if this provider should handle the model
|
|
17
|
+
*/
|
|
18
|
+
matches(model: string): boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the upstream base URL for this provider.
|
|
22
|
+
* @param isChatCompletions - Whether the request uses OpenAI chat completions format
|
|
23
|
+
* @param providerConfig - Optional provider configuration for custom base URL
|
|
24
|
+
* @returns Upstream base URL
|
|
25
|
+
*/
|
|
26
|
+
getUpstreamBase(isChatCompletions: boolean, providerConfig?: { baseUrl?: string }): string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract structured response from streaming data.
|
|
30
|
+
* @param raw - Raw SSE stream data
|
|
31
|
+
* @param log - Captured log entry to update
|
|
32
|
+
* @param fallbackModel - Fallback model name if not in stream
|
|
33
|
+
* @param collectChunks - Whether to collect SSE chunks into log.streamingChunks
|
|
34
|
+
* @returns JSON string of structured response
|
|
35
|
+
*/
|
|
36
|
+
extractStream(
|
|
37
|
+
raw: string,
|
|
38
|
+
log: CapturedLog,
|
|
39
|
+
fallbackModel?: string,
|
|
40
|
+
collectChunks?: boolean,
|
|
41
|
+
): string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional request transformation hook.
|
|
45
|
+
* @param body - Original request body
|
|
46
|
+
* @param apiFormat - Request format (anthropic/openai/unknown)
|
|
47
|
+
* @returns Transformed body or null to use original
|
|
48
|
+
*/
|
|
49
|
+
transformRequest?(body: string, apiFormat: RequestFormat): string | null;
|
|
50
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ProviderProtocol } from "./protocol";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provider Registry
|
|
5
|
+
*
|
|
6
|
+
* Singleton registry for provider implementations.
|
|
7
|
+
* Providers register themselves at module load time.
|
|
8
|
+
*/
|
|
9
|
+
class ProviderRegistryImpl {
|
|
10
|
+
private providers: ProviderProtocol[] = [];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register a provider with the registry.
|
|
14
|
+
* @param provider - Provider implementation to register
|
|
15
|
+
*/
|
|
16
|
+
register(provider: ProviderProtocol): void {
|
|
17
|
+
this.providers.push(provider);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Find the first provider that matches the given model.
|
|
22
|
+
* @param model - Model name to match
|
|
23
|
+
* @returns Matching provider or null if none found
|
|
24
|
+
*/
|
|
25
|
+
findProvider(model: string): ProviderProtocol | null {
|
|
26
|
+
let fallback: ProviderProtocol | null = null;
|
|
27
|
+
|
|
28
|
+
for (const provider of this.providers) {
|
|
29
|
+
if (provider.matches(model)) {
|
|
30
|
+
// Non-anthropic providers win immediately (explicit match)
|
|
31
|
+
if (provider.name !== "anthropic") {
|
|
32
|
+
return provider;
|
|
33
|
+
}
|
|
34
|
+
// Anthropic is the fallback — only use if nothing else matches
|
|
35
|
+
fallback = provider;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get all registered providers.
|
|
43
|
+
* @returns Array of all registered providers
|
|
44
|
+
*/
|
|
45
|
+
getAll(): ProviderProtocol[] {
|
|
46
|
+
return [...this.providers];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Global registry instance */
|
|
51
|
+
export const registry = new ProviderRegistryImpl();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { FormatHandler } from "./handler";
|
|
2
|
+
import type { RequestFormat } from "../schemas";
|
|
3
|
+
import { PATH_V1_CHAT_COMPLETIONS, PATH_CHAT_COMPLETIONS, PATH_V1_MESSAGES } from "../constants";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format registry — maps request paths and formats to their FormatHandler.
|
|
7
|
+
*
|
|
8
|
+
* Registered at module load time by each format's init module.
|
|
9
|
+
*/
|
|
10
|
+
class FormatRegistryImpl {
|
|
11
|
+
private handlers = new Map<RequestFormat, FormatHandler>();
|
|
12
|
+
private pathMap = new Map<string, RequestFormat>();
|
|
13
|
+
|
|
14
|
+
register(handler: FormatHandler): void {
|
|
15
|
+
this.handlers.set(handler.format, handler);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
registerPath(path: string, format: RequestFormat): void {
|
|
19
|
+
this.pathMap.set(path, format);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Get handler by format identifier */
|
|
23
|
+
get(format: RequestFormat): FormatHandler | undefined {
|
|
24
|
+
return this.handlers.get(format);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Get handler matching a request path */
|
|
28
|
+
getByPath(path: string): FormatHandler | undefined {
|
|
29
|
+
const messagesPath = path.split("?")[0] ?? "";
|
|
30
|
+
const format = this.pathMap.get(messagesPath);
|
|
31
|
+
return format === undefined ? undefined : this.handlers.get(format);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Detect format from request body content */
|
|
35
|
+
detectFormat(rawBody: string | null): RequestFormat {
|
|
36
|
+
if (rawBody === null) return "unknown";
|
|
37
|
+
for (const handler of this.handlers.values()) {
|
|
38
|
+
if (handler.detectFormat(rawBody)) return handler.format;
|
|
39
|
+
}
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const formatRegistry = new FormatRegistryImpl();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the FormatHandler for a request path.
|
|
48
|
+
* Returns null if no format matches the path.
|
|
49
|
+
*/
|
|
50
|
+
export function formatForPath(path: string): FormatHandler | null {
|
|
51
|
+
return formatRegistry.getByPath(path) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the wire format from the request endpoint. The endpoint is
|
|
56
|
+
* authoritative because Anthropic and OpenAI request bodies overlap for
|
|
57
|
+
* simple message-only calls.
|
|
58
|
+
*/
|
|
59
|
+
export function requestFormatForPath(path: string): RequestFormat {
|
|
60
|
+
return formatForPath(path)?.format ?? "unknown";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Register known paths
|
|
64
|
+
formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
|
|
65
|
+
formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
|
|
66
|
+
formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");
|