@tonyclaw/llm-inspector 1.6.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/nitro.json +17 -0
- package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
- package/.output/public/assets/index-B3RwBPLW.css +1 -0
- package/.output/public/assets/index-s4lwsWvq.js +97 -0
- package/.output/public/assets/main-Cp8AM0Pa.js +17 -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 +17 -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 +1 -0
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +1 -0
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +1 -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/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/detect-node-es.mjs +1 -0
- package/.output/server/_libs/devlop.mjs +8 -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 +400 -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 +3049 -0
- package/.output/server/_libs/lie.mjs +273 -0
- package/.output/server/_libs/lucide-react.mjs +368 -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/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 +1 -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 +9935 -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 +8 -0
- package/.output/server/_libs/safe-buffer.mjs +64 -0
- package/.output/server/_libs/semver.mjs +1984 -0
- package/.output/server/_libs/seroval-plugins.mjs +58 -0
- package/.output/server/_libs/seroval.mjs +1765 -0
- package/.output/server/_libs/setimmediate.mjs +1 -0
- package/.output/server/_libs/space-separated-tokens.mjs +6 -0
- package/.output/server/_libs/srvx.mjs +334 -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/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 +576 -0
- package/.output/server/_libs/ufo.mjs +54 -0
- package/.output/server/_libs/uint8array-extras.mjs +69 -0
- package/.output/server/_libs/unctx.mjs +1 -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 +1 -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 +4460 -0
- package/.output/server/_ssr/index-ByCLZu7J.mjs +3061 -0
- package/.output/server/_ssr/index.mjs +1176 -0
- package/.output/server/_ssr/router-Bq_mxeNz.mjs +2872 -0
- package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-C4E0e9my.mjs +4 -0
- package/.output/server/index.mjs +393 -0
- package/README.md +196 -0
- package/package.json +91 -0
- package/src/assets/logos/alibaba.svg +1 -0
- package/src/assets/logos/anthropic.svg +1 -0
- package/src/assets/logos/deepseek.svg +1 -0
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +1 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/assets/logos/zhipuai.svg +219 -0
- package/src/cli.ts +68 -0
- package/src/components/ProxyViewer.tsx +325 -0
- package/src/components/ProxyViewerContainer.tsx +211 -0
- package/src/components/providers/ProviderCard.tsx +186 -0
- package/src/components/providers/ProviderForm.tsx +259 -0
- package/src/components/providers/ProviderLogo.tsx +111 -0
- package/src/components/providers/ProvidersPanel.tsx +259 -0
- package/src/components/providers/SettingsDialog.tsx +39 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +68 -0
- package/src/components/proxy-viewer/ConversationHeader.tsx +141 -0
- package/src/components/proxy-viewer/LogEntry.tsx +225 -0
- package/src/components/proxy-viewer/LogEntryHeader.tsx +250 -0
- package/src/components/proxy-viewer/ReplayDialog.tsx +208 -0
- package/src/components/proxy-viewer/ResponseView.tsx +161 -0
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +171 -0
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +139 -0
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +64 -0
- package/src/components/proxy-viewer/formats/index.tsx +24 -0
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +80 -0
- package/src/components/proxy-viewer/index.ts +8 -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/dialog.tsx +129 -0
- package/src/components/ui/json-viewer.tsx +464 -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/lib/export-logs.ts +51 -0
- package/src/lib/utils.ts +22 -0
- package/src/proxy/chunkStorage.ts +118 -0
- package/src/proxy/constants.ts +36 -0
- package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
- package/src/proxy/formats/anthropic/handler.ts +74 -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 +217 -0
- package/src/proxy/formats/anthropic/stream.ts +167 -0
- package/src/proxy/formats/handler.ts +46 -0
- package/src/proxy/formats/index.ts +12 -0
- package/src/proxy/formats/jsonSchema.ts +24 -0
- package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
- package/src/proxy/formats/openai/handler.ts +70 -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 +150 -0
- package/src/proxy/formats/openai/stream.ts +153 -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 +61 -0
- package/src/proxy/handler.ts +389 -0
- package/src/proxy/logIndex.ts +187 -0
- package/src/proxy/logger.ts +99 -0
- package/src/proxy/providers.ts +234 -0
- package/src/proxy/schemas.ts +160 -0
- package/src/proxy/socketTracker.ts +158 -0
- package/src/proxy/store.ts +386 -0
- package/src/router.tsx +16 -0
- package/src/routes/__root.tsx +38 -0
- package/src/routes/api/config.paths.ts +14 -0
- package/src/routes/api/health.ts +11 -0
- package/src/routes/api/logs.$id.chunks.ts +36 -0
- package/src/routes/api/logs.$id.replay.ts +262 -0
- package/src/routes/api/logs.$id.ts +22 -0
- package/src/routes/api/logs.stream.ts +64 -0
- package/src/routes/api/logs.ts +30 -0
- package/src/routes/api/models.ts +10 -0
- package/src/routes/api/providers.$providerId.ts +45 -0
- package/src/routes/api/providers.ts +37 -0
- package/src/routes/api/sessions.ts +10 -0
- package/src/routes/index.tsx +6 -0
- package/src/routes/proxy/$.ts +15 -0
- package/styles/globals.css +121 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { FormatHandler, ParsedRequest } from "../handler";
|
|
2
|
+
import type { CapturedLog, TokenUsage } from "../../schemas";
|
|
3
|
+
import { OpenAIRequestSchema, OpenAIResponseSchema, parseOpenAIResponse } from "./schemas";
|
|
4
|
+
import { extractOpenAIStream } from "./stream";
|
|
5
|
+
|
|
6
|
+
export const OpenAIFormatHandler: FormatHandler = {
|
|
7
|
+
format: "openai",
|
|
8
|
+
|
|
9
|
+
parseRequest(rawBody: string, headers?: Headers): ParsedRequest | null {
|
|
10
|
+
try {
|
|
11
|
+
const json: unknown = JSON.parse(rawBody);
|
|
12
|
+
const result = OpenAIRequestSchema.safeParse(json);
|
|
13
|
+
if (result.success) {
|
|
14
|
+
return {
|
|
15
|
+
model: result.data.model,
|
|
16
|
+
sessionId: headers?.get("x-session-affinity") ?? null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
extractTokens(responseBody: string): TokenUsage {
|
|
26
|
+
const parsed = parseOpenAIResponse(responseBody);
|
|
27
|
+
if (parsed) {
|
|
28
|
+
return {
|
|
29
|
+
inputTokens: parsed.usage.prompt_tokens,
|
|
30
|
+
outputTokens: parsed.usage.completion_tokens,
|
|
31
|
+
cacheCreationInputTokens: null,
|
|
32
|
+
cacheReadInputTokens: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
inputTokens: null,
|
|
37
|
+
outputTokens: null,
|
|
38
|
+
cacheCreationInputTokens: null,
|
|
39
|
+
cacheReadInputTokens: null,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
extractStream(
|
|
44
|
+
raw: string,
|
|
45
|
+
log: CapturedLog,
|
|
46
|
+
fallbackModel?: string,
|
|
47
|
+
collectChunks?: boolean,
|
|
48
|
+
): string {
|
|
49
|
+
return extractOpenAIStream(raw, log, fallbackModel, collectChunks);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
detectFormat(rawBody: string | null): boolean {
|
|
53
|
+
if (rawBody === null) return false;
|
|
54
|
+
try {
|
|
55
|
+
const json: unknown = JSON.parse(rawBody);
|
|
56
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
57
|
+
const keys = Object.keys(json);
|
|
58
|
+
if (keys.includes("model") && keys.includes("messages")) {
|
|
59
|
+
// OpenAI doesn't use "system" or "tools" keys at root level
|
|
60
|
+
if (!keys.includes("system") && !keys.includes("tools")) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Register format handler
|
|
2
|
+
import "./register";
|
|
3
|
+
|
|
4
|
+
// Schemas
|
|
5
|
+
export {
|
|
6
|
+
OpenAIRequestSchema,
|
|
7
|
+
OpenAIResponseSchema,
|
|
8
|
+
OpenAISSERawChunkSchema,
|
|
9
|
+
OpenAIMessage,
|
|
10
|
+
OpenAIMessageContent,
|
|
11
|
+
OpenAIToolDefinition,
|
|
12
|
+
OpenAIChoice,
|
|
13
|
+
OpenAIChoiceDelta,
|
|
14
|
+
parseOpenAIResponse,
|
|
15
|
+
} from "./schemas";
|
|
16
|
+
|
|
17
|
+
// Handler
|
|
18
|
+
export { OpenAIFormatHandler } from "./handler";
|
|
19
|
+
|
|
20
|
+
// Stream extraction
|
|
21
|
+
export { extractOpenAIStream } from "./stream";
|
|
22
|
+
|
|
23
|
+
// Providers
|
|
24
|
+
export { openaiProvider } from "./provider";
|
|
25
|
+
export { alibabaProvider } from "./alibabaProvider";
|
|
@@ -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,150 @@
|
|
|
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
|
+
});
|
|
25
|
+
|
|
26
|
+
export const OpenAIFunctionCall = z.object({
|
|
27
|
+
name: z.string(),
|
|
28
|
+
arguments: z.string(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const OpenAIMessageWithFunctionCall = OpenAIMessage.extend({
|
|
32
|
+
content: z
|
|
33
|
+
.union([z.string(), z.array(z.object({ type: z.literal("text"), text: z.string() }))])
|
|
34
|
+
.optional(),
|
|
35
|
+
function_call: OpenAIFunctionCall.optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const OpenAIToolDefinition = z.object({
|
|
39
|
+
type: z.literal("function"),
|
|
40
|
+
function: z.object({
|
|
41
|
+
name: z.string(),
|
|
42
|
+
description: z.string().optional(),
|
|
43
|
+
parameters: z.record(z.string(), JsonValueSchema),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const OpenAIRequestSchema = z.object({
|
|
48
|
+
model: z.string(),
|
|
49
|
+
messages: z.array(OpenAIMessage),
|
|
50
|
+
temperature: z.number().optional(),
|
|
51
|
+
max_tokens: z.number().optional(),
|
|
52
|
+
stream: z.boolean().optional(),
|
|
53
|
+
tools: z.array(OpenAIToolDefinition).optional(),
|
|
54
|
+
tool_choice: z
|
|
55
|
+
.union([
|
|
56
|
+
z.object({ type: z.literal("auto") }),
|
|
57
|
+
z.object({ type: z.literal("none") }),
|
|
58
|
+
z.object({ type: z.literal("function"), function: z.object({ name: z.string() }) }),
|
|
59
|
+
])
|
|
60
|
+
.optional(),
|
|
61
|
+
user: z.string().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const OpenAIChoiceDelta = z.object({
|
|
65
|
+
role: z.enum(["assistant"]).optional(),
|
|
66
|
+
content: z.string().nullable().optional(),
|
|
67
|
+
reasoning_content: z.string().nullable().optional(),
|
|
68
|
+
function_call: z
|
|
69
|
+
.object({ name: z.string().optional(), arguments: z.string().optional() })
|
|
70
|
+
.optional(),
|
|
71
|
+
tool_calls: z
|
|
72
|
+
.array(
|
|
73
|
+
z.object({
|
|
74
|
+
index: z.number(),
|
|
75
|
+
id: z.string().optional(),
|
|
76
|
+
type: z.literal("function").optional(),
|
|
77
|
+
function: z.object({
|
|
78
|
+
name: z.string().optional(),
|
|
79
|
+
arguments: z.string().optional(),
|
|
80
|
+
}),
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
.optional(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const OpenAIChoice = z.object({
|
|
87
|
+
index: z.number(),
|
|
88
|
+
message: z
|
|
89
|
+
.object({
|
|
90
|
+
role: z.enum(["assistant"]),
|
|
91
|
+
content: z.string().nullable(),
|
|
92
|
+
reasoning_content: z.string().optional(),
|
|
93
|
+
function_call: z.object({ name: z.string(), arguments: z.string() }).optional(),
|
|
94
|
+
})
|
|
95
|
+
.optional(),
|
|
96
|
+
delta: OpenAIChoiceDelta.optional(),
|
|
97
|
+
finish_reason: z.string().nullable(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const OpenAIResponseSchema = z.object({
|
|
101
|
+
id: z.string(),
|
|
102
|
+
object: z.literal("chat.completion"),
|
|
103
|
+
created: z.number(),
|
|
104
|
+
model: z.string(),
|
|
105
|
+
choices: z.array(OpenAIChoice),
|
|
106
|
+
usage: z.object({
|
|
107
|
+
prompt_tokens: z.number(),
|
|
108
|
+
completion_tokens: z.number(),
|
|
109
|
+
total_tokens: z.number(),
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Schema for actual OpenAI SSE chunk format (not wrapped in event/data)
|
|
114
|
+
export const OpenAISSERawChunkSchema = z.object({
|
|
115
|
+
id: z.string(),
|
|
116
|
+
object: z.literal("chat.completion.chunk"),
|
|
117
|
+
created: z.number(),
|
|
118
|
+
model: z.string(),
|
|
119
|
+
choices: z.array(
|
|
120
|
+
z.object({
|
|
121
|
+
index: z.number(),
|
|
122
|
+
delta: OpenAIChoiceDelta,
|
|
123
|
+
finish_reason: z.string().nullable().optional(),
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
usage: z
|
|
127
|
+
.object({
|
|
128
|
+
prompt_tokens: z.number(),
|
|
129
|
+
completion_tokens: z.number(),
|
|
130
|
+
total_tokens: z.number(),
|
|
131
|
+
})
|
|
132
|
+
.nullable()
|
|
133
|
+
.optional(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export type OpenAIResponse = z.infer<typeof OpenAIResponseSchema>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parse an OpenAI response body.
|
|
140
|
+
*/
|
|
141
|
+
export function parseOpenAIResponse(rawBody: string): OpenAIResponse | null {
|
|
142
|
+
try {
|
|
143
|
+
const json: unknown = JSON.parse(rawBody);
|
|
144
|
+
const result = OpenAIResponseSchema.safeParse(json);
|
|
145
|
+
if (result.success) return result.data;
|
|
146
|
+
return null;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { CapturedLog, JsonValue } from "../../schemas";
|
|
2
|
+
import { JsonValueSchema } 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
|
+
const toolCalls: Array<{
|
|
25
|
+
index: number;
|
|
26
|
+
id?: string;
|
|
27
|
+
type: "function";
|
|
28
|
+
function: { name: string; arguments: string };
|
|
29
|
+
}> = [];
|
|
30
|
+
|
|
31
|
+
// Chunk collection state
|
|
32
|
+
const MAX_CHUNKS = 1000;
|
|
33
|
+
let chunkIndex = 0;
|
|
34
|
+
let streamStartMs = 0;
|
|
35
|
+
const chunks: Array<{ index: number; timestamp: number; type: string; data: JsonValue }> = [];
|
|
36
|
+
|
|
37
|
+
for (const line of raw.split("\n")) {
|
|
38
|
+
if (!line.startsWith("data: ")) continue;
|
|
39
|
+
const dataStr = line.slice(6).trim();
|
|
40
|
+
if (dataStr === "[DONE]") break;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const parsed: unknown = JSON.parse(dataStr);
|
|
44
|
+
const chunkResult = OpenAISSERawChunkSchema.safeParse(parsed);
|
|
45
|
+
if (!chunkResult.success) continue;
|
|
46
|
+
const chunk = chunkResult.data;
|
|
47
|
+
|
|
48
|
+
// Track stream start for timestamps
|
|
49
|
+
if (chunkIndex === 0) streamStartMs = Date.now();
|
|
50
|
+
|
|
51
|
+
// Collect chunks if enabled
|
|
52
|
+
if (collectChunks === true && chunks.length < MAX_CHUNKS) {
|
|
53
|
+
const jsonResult = JsonValueSchema.safeParse(chunk);
|
|
54
|
+
if (jsonResult.success) {
|
|
55
|
+
chunks.push({
|
|
56
|
+
index: chunkIndex,
|
|
57
|
+
timestamp: Date.now() - streamStartMs,
|
|
58
|
+
type: "chat.completion.chunk",
|
|
59
|
+
data: jsonResult.data,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
chunkIndex++;
|
|
64
|
+
|
|
65
|
+
if (!started) {
|
|
66
|
+
id = chunk.id;
|
|
67
|
+
model = chunk.model;
|
|
68
|
+
started = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!usageCaptured && chunk.usage !== undefined && chunk.usage !== null) {
|
|
72
|
+
promptTokens = chunk.usage.prompt_tokens;
|
|
73
|
+
completionTokens = chunk.usage.completion_tokens;
|
|
74
|
+
log.inputTokens = promptTokens;
|
|
75
|
+
usageCaptured = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const choice of chunk.choices) {
|
|
79
|
+
const delta = choice.delta;
|
|
80
|
+
if (delta.content !== undefined && delta.content !== null) {
|
|
81
|
+
completionContent += delta.content;
|
|
82
|
+
}
|
|
83
|
+
if (delta.reasoning_content !== undefined && delta.reasoning_content !== null) {
|
|
84
|
+
reasoningContent += delta.reasoning_content;
|
|
85
|
+
}
|
|
86
|
+
if (choice.finish_reason !== undefined && choice.finish_reason !== null) {
|
|
87
|
+
finishReason = choice.finish_reason;
|
|
88
|
+
}
|
|
89
|
+
// Accumulate tool_calls
|
|
90
|
+
if (delta.tool_calls !== undefined && delta.tool_calls !== null) {
|
|
91
|
+
for (const tc of delta.tool_calls) {
|
|
92
|
+
// Find or create tool call entry by index
|
|
93
|
+
let existing = toolCalls.find((t) => t.index === tc.index);
|
|
94
|
+
if (!existing) {
|
|
95
|
+
existing = {
|
|
96
|
+
index: tc.index,
|
|
97
|
+
type: "function",
|
|
98
|
+
function: { name: "", arguments: "" },
|
|
99
|
+
};
|
|
100
|
+
toolCalls.push(existing);
|
|
101
|
+
}
|
|
102
|
+
if (tc.id !== undefined) existing.id = tc.id;
|
|
103
|
+
if (tc.function?.name !== undefined) existing.function.name += tc.function.name;
|
|
104
|
+
if (tc.function?.arguments !== undefined)
|
|
105
|
+
existing.function.arguments += tc.function.arguments;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// non-JSON SSE line, skip
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Store collected chunks on the log
|
|
115
|
+
if (collectChunks === true) {
|
|
116
|
+
log.streamingChunks = {
|
|
117
|
+
chunks,
|
|
118
|
+
truncated: chunkIndex > MAX_CHUNKS,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!usageCaptured) {
|
|
123
|
+
completionTokens = Math.ceil(completionContent.length / 4);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
log.outputTokens = completionTokens;
|
|
127
|
+
|
|
128
|
+
const message: Record<string, unknown> = {
|
|
129
|
+
role: "assistant",
|
|
130
|
+
content: completionContent,
|
|
131
|
+
};
|
|
132
|
+
if (reasoningContent) message.reasoning_content = reasoningContent;
|
|
133
|
+
if (toolCalls.length > 0) message.tool_calls = toolCalls;
|
|
134
|
+
|
|
135
|
+
return JSON.stringify({
|
|
136
|
+
id,
|
|
137
|
+
object: "chat.completion",
|
|
138
|
+
created: Math.floor(Date.now() / 1000),
|
|
139
|
+
model: model !== "" ? model : (fallbackModel ?? ""),
|
|
140
|
+
choices: [
|
|
141
|
+
{
|
|
142
|
+
index: 0,
|
|
143
|
+
message,
|
|
144
|
+
finish_reason: finishReason,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
usage: {
|
|
148
|
+
prompt_tokens: promptTokens,
|
|
149
|
+
completion_tokens: completionTokens,
|
|
150
|
+
total_tokens: promptTokens + completionTokens,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
@@ -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,61 @@
|
|
|
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
|
+
// DeepSeek /v1/messages is chat completions replay
|
|
31
|
+
if (messagesPath === PATH_V1_MESSAGES) return this.handlers.get("anthropic");
|
|
32
|
+
if (messagesPath === PATH_V1_CHAT_COMPLETIONS || messagesPath === PATH_CHAT_COMPLETIONS) {
|
|
33
|
+
return this.handlers.get("openai");
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Detect format from request body content */
|
|
39
|
+
detectFormat(rawBody: string | null): RequestFormat {
|
|
40
|
+
if (rawBody === null) return "unknown";
|
|
41
|
+
for (const handler of this.handlers.values()) {
|
|
42
|
+
if (handler.detectFormat(rawBody)) return handler.format;
|
|
43
|
+
}
|
|
44
|
+
return "unknown";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const formatRegistry = new FormatRegistryImpl();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the FormatHandler for a request path.
|
|
52
|
+
* Returns null if no format matches the path.
|
|
53
|
+
*/
|
|
54
|
+
export function formatForPath(path: string): FormatHandler | null {
|
|
55
|
+
return formatRegistry.getByPath(path) ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Register known paths
|
|
59
|
+
formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
|
|
60
|
+
formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
|
|
61
|
+
formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");
|